mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Add Scrollable widget support, implement disclaimer screen, rearrangements of main menu ui code
This commit is contained in:
parent
01eb061a83
commit
e2bf974ecb
@ -255,7 +255,7 @@ impl PlayState for MainMenuState {
|
||||
},
|
||||
MainMenuEvent::Settings => {}, // TODO
|
||||
MainMenuEvent::Quit => return PlayStateResult::Shutdown,
|
||||
/*MainMenuEvent::DisclaimerClosed => {
|
||||
/*MainMenuEvent::DisclaimerAccepted => {
|
||||
global_state.settings.show_disclaimer = false
|
||||
},*/
|
||||
MainMenuEvent::AuthServerTrust(auth_server, trust) => {
|
||||
|
@ -3,12 +3,10 @@ use crate::{
|
||||
i18n::Localization,
|
||||
ui::{
|
||||
fonts::IcedFonts as Fonts,
|
||||
ice::{component::neat_button, style, widget::image, Element},
|
||||
ice::{component::neat_button, style, Element},
|
||||
},
|
||||
};
|
||||
use iced::{
|
||||
button, Align, Color, Column, Container, HorizontalAlignment, Length, Row, Space, Text,
|
||||
};
|
||||
use iced::{button, Align, Color, Column, Container, Length, Row, Space, Text, VerticalAlignment};
|
||||
|
||||
/// Connecting screen for the main menu
|
||||
pub struct Screen {
|
||||
@ -16,10 +14,6 @@ pub struct Screen {
|
||||
add_button: button::State,
|
||||
}
|
||||
|
||||
// TODO: move to super and unify with identical login consts
|
||||
const TEXT_COLOR: iced::Color = iced::Color::from_rgb(1.0, 1.0, 1.0);
|
||||
const DISABLED_TEXT_COLOR: iced::Color = iced::Color::from_rgba(1.0, 1.0, 1.0, 0.2);
|
||||
|
||||
impl Screen {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
@ -32,32 +26,22 @@ impl Screen {
|
||||
&mut self,
|
||||
fonts: &Fonts,
|
||||
imgs: &Imgs,
|
||||
bg_img: image::Handle,
|
||||
connection_state: &ConnectionState,
|
||||
version: &str,
|
||||
time: f32,
|
||||
i18n: &Localization,
|
||||
button_style: style::button::Style,
|
||||
) -> Element<Message> {
|
||||
let fade_msg = (time * 2.0).sin() * 0.5 + 0.51;
|
||||
let button_style = style::button::Style::new(imgs.button)
|
||||
.hover_image(imgs.button_hover)
|
||||
.press_image(imgs.button_press)
|
||||
.text_color(TEXT_COLOR)
|
||||
.disabled_text_color(DISABLED_TEXT_COLOR);
|
||||
|
||||
let version = Text::new(version)
|
||||
.size(fonts.cyri.scale(15)) // move version text size to const
|
||||
.width(Length::Fill)
|
||||
.height(if matches!(connection_state, ConnectionState::InProgress {..}){Length::Fill}else{Length::Shrink})
|
||||
.horizontal_alignment(HorizontalAlignment::Right);
|
||||
|
||||
let (middle, bottom) = match connection_state {
|
||||
let children = match connection_state {
|
||||
ConnectionState::InProgress { status } => {
|
||||
let status = Text::new(status)
|
||||
.size(fonts.alkhemi.scale(80))
|
||||
.font(fonts.alkhemi.id)
|
||||
.color(Color::from_rgba(1.0, 1.0, 1.0, fade_msg))
|
||||
.width(Length::Fill);
|
||||
.vertical_alignment(VerticalAlignment::Bottom)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill);
|
||||
|
||||
let status = Row::with_children(vec![
|
||||
Space::new(Length::Units(80), Length::Shrink).into(),
|
||||
@ -78,7 +62,7 @@ impl Screen {
|
||||
.center_x()
|
||||
.padding(3);
|
||||
|
||||
(status.into(), cancel.into())
|
||||
vec![status.into(), cancel.into()]
|
||||
},
|
||||
ConnectionState::AuthTrustPrompt { msg, .. } => {
|
||||
let text = Text::new(msg).size(fonts.cyri.scale(25));
|
||||
@ -125,22 +109,16 @@ impl Screen {
|
||||
.center_x()
|
||||
.center_y();
|
||||
|
||||
(
|
||||
vec![
|
||||
container.into(),
|
||||
Space::new(Length::Fill, Length::Units(fonts.cyri.scale(15))).into(),
|
||||
)
|
||||
]
|
||||
},
|
||||
};
|
||||
|
||||
let content = Column::with_children(vec![version.into(), middle, bottom])
|
||||
Column::with_children(children)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.padding(3);
|
||||
|
||||
// Note: could replace this with styling on iced's container since we aren't
|
||||
// using fixed aspect ratio
|
||||
Container::new(content)
|
||||
.style(style::container::Style::image(bg_img))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
78
voxygen/src/menu/main/ui/disclaimer.rs
Normal file
78
voxygen/src/menu/main/ui/disclaimer.rs
Normal file
@ -0,0 +1,78 @@
|
||||
use super::{IcedImgs as Imgs, Message};
|
||||
use crate::{
|
||||
i18n::Localization,
|
||||
ui::{
|
||||
fonts::IcedFonts as Fonts,
|
||||
ice::{component::neat_button, style, Element},
|
||||
},
|
||||
};
|
||||
use iced::{button, scrollable, Column, Container, Length, Scrollable, Space};
|
||||
use vek::Rgba;
|
||||
|
||||
/// Connecting screen for the main menu
|
||||
pub struct Screen {
|
||||
accept_button: button::State,
|
||||
scroll: scrollable::State,
|
||||
}
|
||||
|
||||
impl Screen {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
accept_button: Default::default(),
|
||||
scroll: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn view(
|
||||
&mut self,
|
||||
fonts: &Fonts,
|
||||
imgs: &Imgs,
|
||||
i18n: &Localization,
|
||||
button_style: style::button::Style,
|
||||
) -> Element<Message> {
|
||||
Container::new(
|
||||
Container::new(
|
||||
Column::with_children(vec![
|
||||
iced::Text::new(i18n.get("common.disclaimer"))
|
||||
.font(fonts.alkhemi.id)
|
||||
.size(fonts.alkhemi.scale(35))
|
||||
.into(),
|
||||
Space::new(Length::Fill, Length::Units(20)).into(),
|
||||
Scrollable::new(&mut self.scroll)
|
||||
.push(
|
||||
iced::Text::new(i18n.get("main.notice"))
|
||||
.font(fonts.cyri.id)
|
||||
.size(fonts.cyri.scale(23)),
|
||||
)
|
||||
.height(Length::FillPortion(1))
|
||||
.into(),
|
||||
Container::new(
|
||||
Container::new(neat_button(
|
||||
&mut self.accept_button,
|
||||
i18n.get("common.accept"),
|
||||
0.7,
|
||||
button_style,
|
||||
Some(Message::AcceptDisclaimer),
|
||||
))
|
||||
.height(Length::Units(fonts.cyri.scale(50))),
|
||||
)
|
||||
.center_x()
|
||||
.height(Length::Shrink)
|
||||
.width(Length::Fill)
|
||||
.into(),
|
||||
])
|
||||
.spacing(5)
|
||||
.padding(20)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill),
|
||||
)
|
||||
.style(style::container::Style::Color(Rgba::new(22, 19, 17, 255))),
|
||||
)
|
||||
.center_x()
|
||||
.center_y()
|
||||
.padding(70)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
use super::{IcedImgs as Imgs, Info, LoginInfo, Message};
|
||||
use super::{IcedImgs as Imgs, LoginInfo, Message};
|
||||
use crate::{
|
||||
i18n::Localization,
|
||||
ui::{
|
||||
@ -14,14 +14,11 @@ use crate::{
|
||||
},
|
||||
},
|
||||
};
|
||||
use iced::{
|
||||
button, text_input, Align, Column, Container, HorizontalAlignment, Length, Row, Space, Text,
|
||||
TextInput,
|
||||
};
|
||||
use iced::{button, text_input, Align, Column, Container, Length, Row, Space, Text, TextInput};
|
||||
use vek::*;
|
||||
|
||||
const TEXT_COLOR: iced::Color = iced::Color::from_rgb(1.0, 1.0, 1.0);
|
||||
const DISABLED_TEXT_COLOR: iced::Color = iced::Color::from_rgba(1.0, 1.0, 1.0, 0.2);
|
||||
pub const TEXT_COLOR: iced::Color = iced::Color::from_rgb(1.0, 1.0, 1.0);
|
||||
pub const DISABLED_TEXT_COLOR: iced::Color = iced::Color::from_rgba(1.0, 1.0, 1.0, 0.2);
|
||||
const FILL_FRAC_ONE: f32 = 0.77;
|
||||
const FILL_FRAC_TWO: f32 = 0.53;
|
||||
const INPUT_WIDTH: u16 = 250;
|
||||
@ -52,18 +49,11 @@ impl Screen {
|
||||
fonts: &Fonts,
|
||||
imgs: &Imgs,
|
||||
login_info: &LoginInfo,
|
||||
info: &Info,
|
||||
error: Option<&str>,
|
||||
version: &str,
|
||||
show_servers: bool,
|
||||
i18n: &Localization,
|
||||
button_style: style::button::Style,
|
||||
) -> Element<Message> {
|
||||
let button_style = style::button::Style::new(imgs.button)
|
||||
.hover_image(imgs.button_hover)
|
||||
.press_image(imgs.button_press)
|
||||
.text_color(TEXT_COLOR)
|
||||
.disabled_text_color(DISABLED_TEXT_COLOR);
|
||||
|
||||
let buttons = Column::with_children(vec![
|
||||
neat_button(
|
||||
&mut self.servers_button,
|
||||
@ -96,32 +86,28 @@ impl Screen {
|
||||
.height(Length::Fill)
|
||||
.align_y(Align::End);
|
||||
|
||||
let left_column = if matches!(info, Info::Intro) {
|
||||
let intro_text = i18n.get("main.login_process");
|
||||
let intro_text = i18n.get("main.login_process");
|
||||
|
||||
let info_window = BackgroundContainer::new(
|
||||
CompoundGraphic::from_graphics(vec![
|
||||
Graphic::rect(Rgba::new(0, 0, 0, 240), [500, 300], [0, 0]),
|
||||
// Note: a way to tell it to keep the height of this one piece constant and
|
||||
// unstreched would be nice, I suppose we could just break this out into a
|
||||
// column and use Length::Units
|
||||
Graphic::image(imgs.banner_bottom, [500, 30], [0, 300])
|
||||
.color(Rgba::new(255, 255, 255, 240)),
|
||||
])
|
||||
.height(Length::Shrink),
|
||||
Text::new(intro_text).size(fonts.cyri.scale(21)),
|
||||
)
|
||||
.max_width(450)
|
||||
.padding(Padding::new().horizontal(20).top(10).bottom(60));
|
||||
let info_window = BackgroundContainer::new(
|
||||
CompoundGraphic::from_graphics(vec![
|
||||
Graphic::rect(Rgba::new(0, 0, 0, 240), [500, 300], [0, 0]),
|
||||
// Note: a way to tell it to keep the height of this one piece constant and
|
||||
// unstreched would be nice, I suppose we could just break this out into a
|
||||
// column and use Length::Units
|
||||
Graphic::image(imgs.banner_bottom, [500, 30], [0, 300])
|
||||
.color(Rgba::new(255, 255, 255, 240)),
|
||||
])
|
||||
.height(Length::Shrink),
|
||||
Text::new(intro_text).size(fonts.cyri.scale(21)),
|
||||
)
|
||||
.max_width(450)
|
||||
.padding(Padding::new().horizontal(20).top(10).bottom(60));
|
||||
|
||||
Column::with_children(vec![info_window.into(), buttons.into()])
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.padding(27)
|
||||
.into()
|
||||
} else {
|
||||
buttons.into()
|
||||
};
|
||||
let left_column = Column::with_children(vec![info_window.into(), buttons.into()])
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.padding(27)
|
||||
.into();
|
||||
|
||||
let banner = self
|
||||
.banner
|
||||
@ -133,12 +119,9 @@ impl Screen {
|
||||
.align_x(Align::Center)
|
||||
.align_y(Align::Center);
|
||||
|
||||
let right_column = Text::new(version)
|
||||
.size(fonts.cyri.scale(15))
|
||||
.width(Length::Fill)
|
||||
.horizontal_alignment(HorizontalAlignment::Right);
|
||||
let right_column = Space::new(Length::Fill, Length::Fill);
|
||||
|
||||
let content = Row::with_children(vec![
|
||||
Row::with_children(vec![
|
||||
left_column,
|
||||
central_column.into(),
|
||||
right_column.into(),
|
||||
@ -146,11 +129,7 @@ impl Screen {
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.spacing(10)
|
||||
.padding(3);
|
||||
|
||||
Container::new(content)
|
||||
.style(style::container::Style::image(imgs.bg))
|
||||
.into()
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
mod connecting;
|
||||
//mod disclaimer;
|
||||
mod login;
|
||||
|
||||
use crate::{
|
||||
@ -34,7 +35,8 @@ const COL1: Color = Color::Rgba(0.07, 0.1, 0.1, 0.9);
|
||||
/*const UI_MAIN: Color = Color::Rgba(0.61, 0.70, 0.70, 1.0); // Greenish Blue
|
||||
const UI_HIGHLIGHT_0: Color = Color::Rgba(0.79, 1.09, 1.09, 1.0);*/
|
||||
|
||||
use iced::text_input;
|
||||
use iced::{text_input, Column, Container, HorizontalAlignment, Length};
|
||||
use ui::ice::style;
|
||||
image_ids_ice! {
|
||||
struct IcedImgs {
|
||||
<VoxelGraphic>
|
||||
@ -94,6 +96,7 @@ pub enum Event {
|
||||
Quit,
|
||||
Settings,
|
||||
//DisclaimerClosed, TODO: remove all traces?
|
||||
//DisclaimerAccepted,
|
||||
AuthServerTrust(String, bool),
|
||||
}
|
||||
|
||||
@ -114,11 +117,6 @@ pub struct LoginInfo {
|
||||
pub server: String,
|
||||
}
|
||||
|
||||
enum Info {
|
||||
//Disclaimer,
|
||||
Intro,
|
||||
}
|
||||
|
||||
enum ConnectionState {
|
||||
InProgress {
|
||||
status: String,
|
||||
@ -140,6 +138,9 @@ impl ConnectionState {
|
||||
}
|
||||
|
||||
enum Screen {
|
||||
/*Disclaimer {
|
||||
screen: disclaimer::Screen,
|
||||
},*/
|
||||
Login {
|
||||
screen: login::Screen,
|
||||
// Error to display in a box
|
||||
@ -162,7 +163,6 @@ struct IcedState {
|
||||
login_info: LoginInfo,
|
||||
|
||||
show_servers: bool,
|
||||
info: Info,
|
||||
time: f32,
|
||||
|
||||
screen: Screen,
|
||||
@ -183,7 +183,8 @@ enum Message {
|
||||
TrustPromptAdd,
|
||||
TrustPromptCancel,
|
||||
CloseError,
|
||||
//CloseDisclaimer,
|
||||
/*CloseDisclaimer,
|
||||
*AcceptDisclaimer, */
|
||||
}
|
||||
|
||||
impl IcedState {
|
||||
@ -200,10 +201,15 @@ impl IcedState {
|
||||
common::util::GIT_VERSION.to_string()
|
||||
);
|
||||
|
||||
let info = Info::Intro; // if settings.show_disclaimer {
|
||||
//Info::Disclaimer
|
||||
//} else {
|
||||
//Info::Intro
|
||||
let screen = /* if settings.show_disclaimer {
|
||||
Screen::Disclaimer {
|
||||
screen: disclaimer::Screen::new(),
|
||||
}
|
||||
} else { */
|
||||
Screen::Login {
|
||||
screen: login::Screen::new(),
|
||||
error: None,
|
||||
};
|
||||
//};
|
||||
|
||||
Self {
|
||||
@ -220,30 +226,47 @@ impl IcedState {
|
||||
},
|
||||
|
||||
show_servers: false,
|
||||
info,
|
||||
time: 0.0,
|
||||
|
||||
screen: Screen::Login {
|
||||
screen: login::Screen::new(),
|
||||
error: None,
|
||||
},
|
||||
screen,
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&mut self, dt: f32) -> Element<Message> {
|
||||
self.time = self.time + dt;
|
||||
|
||||
// TODO: make disclaimer it's own screen?
|
||||
match &mut self.screen {
|
||||
// TODO: consider setting this as the default in the renderer
|
||||
let button_style = style::button::Style::new(self.imgs.button)
|
||||
.hover_image(self.imgs.button_hover)
|
||||
.press_image(self.imgs.button_press)
|
||||
.text_color(login::TEXT_COLOR)
|
||||
.disabled_text_color(login::DISABLED_TEXT_COLOR);
|
||||
|
||||
let version = iced::Text::new(&self.version)
|
||||
.size(self.fonts.cyri.scale(15))
|
||||
.width(Length::Fill)
|
||||
.horizontal_alignment(HorizontalAlignment::Right);
|
||||
|
||||
let bg_img = if matches!(&self.screen, Screen::Connecting {..}) {
|
||||
self.bg_img
|
||||
} else {
|
||||
self.imgs.bg
|
||||
};
|
||||
|
||||
// TODO: make any large text blocks scrollable so that if the area is to
|
||||
// small they can still be read
|
||||
let content = match &mut self.screen {
|
||||
/*Screen::Disclaimer { screen } => {
|
||||
screen.view(&self.fonts, &self.imgs, &self.i18n, button_style)
|
||||
},*/
|
||||
Screen::Login { screen, error } => screen.view(
|
||||
&self.fonts,
|
||||
&self.imgs,
|
||||
&self.login_info,
|
||||
&self.info,
|
||||
error.as_deref(),
|
||||
&self.version,
|
||||
self.show_servers,
|
||||
&self.i18n,
|
||||
button_style,
|
||||
),
|
||||
Screen::Connecting {
|
||||
screen,
|
||||
@ -251,13 +274,22 @@ impl IcedState {
|
||||
} => screen.view(
|
||||
&self.fonts,
|
||||
&self.imgs,
|
||||
self.bg_img,
|
||||
&connection_state,
|
||||
&self.version,
|
||||
self.time,
|
||||
&self.i18n,
|
||||
button_style,
|
||||
),
|
||||
}
|
||||
};
|
||||
|
||||
Container::new(
|
||||
Column::with_children(vec![version.into(), content])
|
||||
.spacing(3)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill),
|
||||
)
|
||||
.style(style::container::Style::image(bg_img))
|
||||
.padding(3)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Message, events: &mut Vec<Event>) {
|
||||
@ -330,6 +362,15 @@ impl IcedState {
|
||||
//Message::CloseDisclaimer => {
|
||||
// events.push(Event::DisclaimerClosed);
|
||||
//},
|
||||
/*Message::AcceptDisclaimer => {
|
||||
if let Screen::Disclaimer { .. } = &self.screen {
|
||||
events.push(Event::DisclaimerAccepted);
|
||||
self.screen = Screen::Login {
|
||||
screen: login::Screen::new(),
|
||||
error: None,
|
||||
};
|
||||
}
|
||||
},*/
|
||||
}
|
||||
}
|
||||
|
||||
@ -816,7 +857,7 @@ impl<'a> MainMenuUi {
|
||||
.was_clicked()
|
||||
{
|
||||
self.show_disclaimer = false;
|
||||
events.push(Event::DisclaimerClosed);
|
||||
events.push(Event::DisclaimerAccepted);
|
||||
}
|
||||
} else {*/
|
||||
// TODO: Don't use macros for this?
|
||||
|
@ -63,6 +63,8 @@ impl IcedUi {
|
||||
use iced::window;
|
||||
match event {
|
||||
// Intercept resizing events
|
||||
// TODO: examine if we are handling dpi properly here
|
||||
// ideally these values should be the logical ones
|
||||
Event::Window(window::Event::Resized { width, height }) => {
|
||||
self.window_resized = Some(Vec2::new(width, height));
|
||||
},
|
||||
|
@ -86,7 +86,7 @@ pub struct IcedRenderer {
|
||||
// Per-frame/update
|
||||
current_state: State,
|
||||
mesh: Mesh<UiPipeline>,
|
||||
glyphs: Vec<(usize, usize, Rgba<f32>)>,
|
||||
glyphs: Vec<(usize, usize, Rgba<f32>, Vec2<u32>)>,
|
||||
// Output from glyph_brush in the previous frame
|
||||
// It can sometimes ask you to redraw with these instead (idk if that is done with
|
||||
// pre-positioned glyphs)
|
||||
@ -164,7 +164,7 @@ impl IcedRenderer {
|
||||
|
||||
//self.current_scissor = default_scissor(renderer);
|
||||
|
||||
self.draw_primitive(primitive, renderer);
|
||||
self.draw_primitive(primitive, Vec2::zero(), renderer);
|
||||
|
||||
// Enter the final command.
|
||||
self.draw_commands.push(match self.current_state {
|
||||
@ -240,19 +240,29 @@ impl IcedRenderer {
|
||||
|
||||
let glyphs = &self.glyphs;
|
||||
let mesh = &mut self.mesh;
|
||||
let p_scale = self.p_scale;
|
||||
let half_res = self.half_res;
|
||||
|
||||
glyphs
|
||||
.iter()
|
||||
.flat_map(|(mesh_index, glyph_count, linear_color)| {
|
||||
.flat_map(|(mesh_index, glyph_count, linear_color, offset)| {
|
||||
let mesh_index = *mesh_index;
|
||||
let linear_color = *linear_color;
|
||||
(0..*glyph_count).map(move |i| (mesh_index + i * 6, linear_color))
|
||||
// Could potentially pass this in as part of the extras
|
||||
let offset = offset.map(|e| e as f32 * p_scale) / half_res;
|
||||
(0..*glyph_count).map(move |i| (mesh_index + i * 6, linear_color, offset))
|
||||
})
|
||||
.zip(self.last_glyph_verts.iter())
|
||||
.for_each(|((mesh_index, linear_color), (uv, rect))| {
|
||||
.for_each(|((mesh_index, linear_color, offset), (uv, rect))| {
|
||||
// TODO: add function to vek for this
|
||||
let rect = Aabr {
|
||||
min: rect.min + offset,
|
||||
max: rect.max + offset,
|
||||
};
|
||||
|
||||
mesh.replace_quad(
|
||||
mesh_index,
|
||||
create_ui_quad(*rect, *uv, linear_color, UiMode::Text),
|
||||
create_ui_quad(rect, *uv, linear_color, UiMode::Text),
|
||||
)
|
||||
});
|
||||
},
|
||||
@ -344,12 +354,12 @@ impl IcedRenderer {
|
||||
Aabr { min, max }
|
||||
}
|
||||
|
||||
fn draw_primitive(&mut self, primitive: Primitive, renderer: &mut Renderer) {
|
||||
fn draw_primitive(&mut self, primitive: Primitive, offset: Vec2<u32>, renderer: &mut Renderer) {
|
||||
match primitive {
|
||||
Primitive::Group { primitives } => {
|
||||
primitives
|
||||
.into_iter()
|
||||
.for_each(|p| self.draw_primitive(p, renderer));
|
||||
.for_each(|p| self.draw_primitive(p, offset, renderer));
|
||||
},
|
||||
Primitive::Image {
|
||||
handle,
|
||||
@ -363,7 +373,11 @@ impl IcedRenderer {
|
||||
}
|
||||
|
||||
let (graphic_id, rotation) = handle;
|
||||
let gl_aabr = self.gl_aabr(bounds);
|
||||
let gl_aabr = self.gl_aabr(iced::Rectangle {
|
||||
x: bounds.x + offset.x as f32,
|
||||
y: bounds.y + offset.y as f32,
|
||||
..bounds
|
||||
});
|
||||
|
||||
let graphic_cache = self.cache.graphic_cache_mut();
|
||||
|
||||
@ -435,8 +449,14 @@ impl IcedRenderer {
|
||||
|
||||
self.switch_state(State::Plain);
|
||||
|
||||
let gl_aabr = self.gl_aabr(iced::Rectangle {
|
||||
x: bounds.x + offset.x as f32,
|
||||
y: bounds.y + offset.y as f32,
|
||||
..bounds
|
||||
});
|
||||
|
||||
self.mesh.push_quad(create_ui_quad(
|
||||
self.gl_aabr(bounds),
|
||||
gl_aabr,
|
||||
Aabr {
|
||||
min: Vec2::zero(),
|
||||
max: Vec2::zero(),
|
||||
@ -470,7 +490,7 @@ impl IcedRenderer {
|
||||
// Since we already passed in `bounds` to position the glyphs some of this
|
||||
// seems redundant...
|
||||
// Note: we can't actually use this because dropping glyphs messeses up the
|
||||
// counting and there is not a convenient method provided to drop out of bounds
|
||||
// counting and there is not a method provided to drop out of bounds
|
||||
// glyphs while positioning them
|
||||
glyph_brush::ab_glyph::Rect {
|
||||
min: glyph_brush::ab_glyph::point(
|
||||
@ -489,8 +509,12 @@ impl IcedRenderer {
|
||||
min: Vec2::broadcast(0.0),
|
||||
max: Vec2::broadcast(0.0),
|
||||
};
|
||||
self.glyphs
|
||||
.push((self.mesh.vertices().len(), glyph_count, linear_color));
|
||||
self.glyphs.push((
|
||||
self.mesh.vertices().len(),
|
||||
glyph_count,
|
||||
linear_color,
|
||||
offset,
|
||||
));
|
||||
for _ in 0..glyph_count {
|
||||
// Push placeholder quad
|
||||
// Note: moving to some sort of layering / z based system would be an
|
||||
@ -504,8 +528,13 @@ impl IcedRenderer {
|
||||
));
|
||||
}
|
||||
},
|
||||
Primitive::Clip { bounds, content } => {
|
||||
Primitive::Clip {
|
||||
bounds,
|
||||
offset: clip_offset,
|
||||
content,
|
||||
} => {
|
||||
let new_scissor = {
|
||||
// TODO: incorporate current offset for nested Clips
|
||||
let intersection = Aabr {
|
||||
min: Vec2 {
|
||||
x: (bounds.x * self.p_scale) as u16,
|
||||
@ -545,7 +574,7 @@ impl IcedRenderer {
|
||||
// TODO: cull primitives outside the current scissor
|
||||
|
||||
// Renderer child
|
||||
self.draw_primitive(*content, renderer);
|
||||
self.draw_primitive(*content, offset + clip_offset, renderer);
|
||||
|
||||
// Reset scissor
|
||||
self.draw_commands.push(match self.current_state {
|
||||
|
@ -25,6 +25,7 @@ pub enum Primitive {
|
||||
},
|
||||
Clip {
|
||||
bounds: iced::Rectangle,
|
||||
offset: vek::Vec2<u32>,
|
||||
content: Box<Primitive>,
|
||||
},
|
||||
Nothing,
|
||||
|
@ -1,2 +1,3 @@
|
||||
pub mod button;
|
||||
pub mod container;
|
||||
pub mod scrollable;
|
||||
|
33
voxygen/src/ui/ice/renderer/style/scrollable.rs
Normal file
33
voxygen/src/ui/ice/renderer/style/scrollable.rs
Normal file
@ -0,0 +1,33 @@
|
||||
use super::super::super::widget::image;
|
||||
use vek::Rgba;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Style {
|
||||
pub track: Option<Track>,
|
||||
pub scroller: Scroller,
|
||||
}
|
||||
|
||||
impl Default for Style {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
track: None,
|
||||
scroller: Scroller::Color(Rgba::new(128, 128, 128, 255)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Track {
|
||||
Color(Rgba<u8>),
|
||||
Image(image::Handle, Rgba<u8>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Scroller {
|
||||
Color(Rgba<u8>),
|
||||
Image {
|
||||
ends: image::Handle,
|
||||
mid: image::Handle,
|
||||
color: Rgba<u8>,
|
||||
},
|
||||
}
|
@ -6,6 +6,7 @@ mod compound_graphic;
|
||||
mod container;
|
||||
mod image;
|
||||
mod row;
|
||||
mod scrollable;
|
||||
mod space;
|
||||
mod text;
|
||||
mod text_input;
|
||||
|
178
voxygen/src/ui/ice/renderer/widget/scrollable.rs
Normal file
178
voxygen/src/ui/ice/renderer/widget/scrollable.rs
Normal file
@ -0,0 +1,178 @@
|
||||
use super::super::{super::Rotation, style, IcedRenderer, Primitive};
|
||||
use common::util::srgba_to_linear;
|
||||
use iced::{mouse, scrollable, Rectangle};
|
||||
use style::scrollable::{Scroller, Track};
|
||||
|
||||
const SCROLLBAR_WIDTH: u16 = 10;
|
||||
const SCROLLBAR_MIN_HEIGHT: u16 = 6;
|
||||
const SCROLLBAR_MARGIN: u16 = 2;
|
||||
|
||||
impl scrollable::Renderer for IcedRenderer {
|
||||
type Style = style::scrollable::Style;
|
||||
|
||||
// Interesting that this is here
|
||||
// I guess we can take advantage of this to keep a constant size despite
|
||||
// scaling?
|
||||
fn scrollbar(
|
||||
&self,
|
||||
bounds: Rectangle,
|
||||
content_bounds: Rectangle,
|
||||
offset: u32,
|
||||
) -> Option<scrollable::Scrollbar> {
|
||||
// TODO: might actually want to divide by p_scale here (same in text&ext_input)
|
||||
// (or just not use it) (or at least only account for dpi but not any
|
||||
// additional scaling)
|
||||
let width = (SCROLLBAR_WIDTH + 2 * SCROLLBAR_MARGIN) as f32 * self.p_scale;
|
||||
if content_bounds.height > bounds.height {
|
||||
let scrollbar_bounds = Rectangle {
|
||||
x: bounds.x + bounds.width - width,
|
||||
width,
|
||||
..bounds
|
||||
};
|
||||
|
||||
let visible_fraction = bounds.height / content_bounds.height;
|
||||
let scrollbar_height = (bounds.height * visible_fraction)
|
||||
.max((2 * SCROLLBAR_MIN_HEIGHT) as f32 * self.p_scale);
|
||||
let y_offset = offset as f32 * visible_fraction;
|
||||
|
||||
let scroller_bounds = Rectangle {
|
||||
x: scrollbar_bounds.x + SCROLLBAR_MARGIN as f32 * self.p_scale,
|
||||
// TODO: check this behavior
|
||||
y: scrollbar_bounds.y + y_offset,
|
||||
width: scrollbar_bounds.width - (2 * SCROLLBAR_MARGIN) as f32 * self.p_scale,
|
||||
height: scrollbar_height,
|
||||
};
|
||||
Some(scrollable::Scrollbar {
|
||||
bounds: scrollbar_bounds,
|
||||
scroller: scrollable::Scroller {
|
||||
bounds: scroller_bounds,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&mut self,
|
||||
state: &scrollable::State,
|
||||
bounds: Rectangle,
|
||||
_content_bounds: Rectangle,
|
||||
is_mouse_over: bool,
|
||||
is_mouse_over_scrollbar: bool,
|
||||
scrollbar: Option<scrollable::Scrollbar>,
|
||||
offset: u32,
|
||||
style_sheet: &Self::Style,
|
||||
(content, mouse_interaction): Self::Output,
|
||||
) -> Self::Output {
|
||||
(
|
||||
if let Some(scrollbar) = scrollbar {
|
||||
let mut primitives = Vec::with_capacity(5);
|
||||
|
||||
// Scrolled content
|
||||
primitives.push(Primitive::Clip {
|
||||
bounds,
|
||||
offset: (0, offset).into(),
|
||||
content: Box::new(content),
|
||||
});
|
||||
|
||||
let style = style_sheet;
|
||||
//let style = if state.is_scroller_grabbed() {
|
||||
// style_sheet.dragging()
|
||||
//} else if is_mouse_over_scrollbar {
|
||||
// style_sheet.hovered()
|
||||
//} else {
|
||||
// style_sheet.active();
|
||||
//};
|
||||
|
||||
let is_scrollbar_visible = style.track.is_some();
|
||||
|
||||
if is_mouse_over || state.is_scroller_grabbed() || is_scrollbar_visible {
|
||||
let bounds = scrollbar.scroller.bounds;
|
||||
|
||||
match style.scroller {
|
||||
Scroller::Color(color) => primitives.push(Primitive::Rectangle {
|
||||
bounds,
|
||||
linear_color: srgba_to_linear(color.map(|e| e as f32 / 255.0)),
|
||||
}),
|
||||
Scroller::Image { ends, mid, color } => {
|
||||
// Calculate sizes of ends pieces based on image aspect ratio
|
||||
let (img_w, img_h) = self.image_dims(ends);
|
||||
let end_height = bounds.width * img_h as f32 / img_w as f32;
|
||||
|
||||
// Calcutate size of middle piece based on available space
|
||||
// Note: Might want to scale into real pixels for parts of this
|
||||
let (end_height, middle_height) =
|
||||
if end_height * 2.0 + 1.0 <= bounds.height {
|
||||
(end_height, bounds.height - end_height * 2.0)
|
||||
} else {
|
||||
// Take 1 logical pixel for the middle height
|
||||
let remaining_height = bounds.height - 1.0;
|
||||
(remaining_height / 2.0, 1.0)
|
||||
};
|
||||
|
||||
// Top
|
||||
primitives.push(Primitive::Image {
|
||||
handle: (ends, Rotation::None),
|
||||
bounds: Rectangle {
|
||||
height: end_height,
|
||||
..bounds
|
||||
},
|
||||
color,
|
||||
});
|
||||
// Middle
|
||||
primitives.push(Primitive::Image {
|
||||
handle: (mid, Rotation::None),
|
||||
bounds: Rectangle {
|
||||
y: bounds.y + end_height,
|
||||
height: middle_height,
|
||||
..bounds
|
||||
},
|
||||
color,
|
||||
});
|
||||
// Bottom
|
||||
primitives.push(Primitive::Image {
|
||||
handle: (ends, Rotation::Cw180),
|
||||
bounds: Rectangle {
|
||||
y: bounds.y + end_height + middle_height,
|
||||
height: end_height,
|
||||
..bounds
|
||||
},
|
||||
color,
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(track) = style.track {
|
||||
let bounds = Rectangle {
|
||||
x: scrollbar.bounds.x + SCROLLBAR_MARGIN as f32 * self.p_scale,
|
||||
width: scrollbar.bounds.width
|
||||
- (2 * SCROLLBAR_MARGIN) as f32 * self.p_scale,
|
||||
..scrollbar.bounds
|
||||
};
|
||||
primitives.push(match track {
|
||||
Track::Color(color) => Primitive::Rectangle {
|
||||
bounds,
|
||||
linear_color: srgba_to_linear(color.map(|e| e as f32 / 255.0)),
|
||||
},
|
||||
Track::Image(handle, color) => Primitive::Image {
|
||||
handle: (handle, Rotation::None),
|
||||
bounds,
|
||||
color,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Primitive::Group { primitives }
|
||||
} else {
|
||||
content
|
||||
},
|
||||
if is_mouse_over_scrollbar || state.is_scroller_grabbed() {
|
||||
mouse::Interaction::Idle
|
||||
} else {
|
||||
mouse_interaction
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
@ -169,7 +169,7 @@ impl text_input::Renderer for IcedRenderer {
|
||||
(
|
||||
Primitive::Rectangle {
|
||||
bounds: Rectangle {
|
||||
x: text_bounds.x + position - offset - CURSOR_WIDTH / p_scale / 2.0,
|
||||
x: text_bounds.x + position - CURSOR_WIDTH / p_scale / 2.0,
|
||||
y: text_bounds.y,
|
||||
width: CURSOR_WIDTH / p_scale,
|
||||
height: text_bounds.height,
|
||||
@ -196,7 +196,7 @@ impl text_input::Renderer for IcedRenderer {
|
||||
(
|
||||
Primitive::Rectangle {
|
||||
bounds: Rectangle {
|
||||
x: text_bounds.x + left_position - left_offset,
|
||||
x: text_bounds.x + left_position,
|
||||
y: text_bounds.y,
|
||||
width,
|
||||
height: text_bounds.height,
|
||||
@ -216,10 +216,9 @@ impl text_input::Renderer for IcedRenderer {
|
||||
|
||||
let display_text = text.unwrap_or(if state.is_focused() { "" } else { placeholder });
|
||||
let section = glyph_brush::Section {
|
||||
screen_position: (
|
||||
text_bounds.x * p_scale - scroll_offset,
|
||||
text_bounds.center_y() * p_scale,
|
||||
),
|
||||
// Note: clip offset is an integer so we don't have to worry about not accounting for
|
||||
// that here where the alignment of the glyphs with pixels affects rasterization
|
||||
screen_position: (text_bounds.x * p_scale, text_bounds.center_y() * p_scale),
|
||||
bounds: (
|
||||
10000.0, /* text_bounds.width * p_scale */
|
||||
text_bounds.height * p_scale,
|
||||
@ -279,9 +278,8 @@ impl text_input::Renderer for IcedRenderer {
|
||||
let primitive = if text_width > text_bounds.width {
|
||||
Primitive::Clip {
|
||||
bounds: text_bounds,
|
||||
offset: (scroll_offset as u32, 0).into(),
|
||||
content: Box::new(primitive),
|
||||
/* Note: iced_wgpu uses offset here but we can't do that since we pass the text
|
||||
* to the glyph_brush here */
|
||||
}
|
||||
} else {
|
||||
primitive
|
||||
|
Loading…
Reference in New Issue
Block a user