From fb871fc76612b9641cd63616f94eb9fabfbc8361 Mon Sep 17 00:00:00 2001 From: Paris DOUADY Date: Thu, 11 Jul 2024 16:06:15 +0200 Subject: [PATCH] add more padding to building select fixes #112 --- goryak/src/imagebutton.rs | 24 +- goryak/src/scroll.rs | 230 ++++++++++++++++- native_app/src/gui/hud/toolbox/building.rs | 284 +++++++++++---------- 3 files changed, 390 insertions(+), 148 deletions(-) diff --git a/goryak/src/imagebutton.rs b/goryak/src/imagebutton.rs index 1e465dcb..a83c1a32 100644 --- a/goryak/src/imagebutton.rs +++ b/goryak/src/imagebutton.rs @@ -146,6 +146,18 @@ impl Widget for ImageButtonWidget { resp } + fn layout(&self, mut ctx: LayoutContext<'_>, input: Constraints) -> Vec2 { + if let Some(tooltip) = ctx.dom.get_current().children.first() { + let size = ctx.calculate_layout(*tooltip, Constraints::none()); + ctx.layout.set_pos( + *tooltip, + Vec2::new((self.props.size.x - size.x) / 2.0, -size.y - 10.0), + ); + } + + input.constrain_min(self.props.size) + } + fn paint(&self, mut ctx: PaintContext<'_>) { let node = ctx.dom.get_current(); let layout_node = ctx.layout.get(ctx.dom.current()).unwrap(); @@ -174,18 +186,6 @@ impl Widget for ImageButtonWidget { EventInterest::MOUSE_ALL } - fn layout(&self, mut ctx: LayoutContext<'_>, input: Constraints) -> Vec2 { - if let Some(tooltip) = ctx.dom.get_current().children.first() { - let size = ctx.calculate_layout(*tooltip, Constraints::none()); - ctx.layout.set_pos( - *tooltip, - Vec2::new((self.props.size.x - size.x) / 2.0, -size.y - 10.0), - ); - } - - input.constrain_min(self.props.size) - } - fn event(&mut self, _: EventContext<'_>, event: &WidgetEvent) -> EventResponse { match *event { WidgetEvent::MouseMoved(Some(_)) => { diff --git a/goryak/src/scroll.rs b/goryak/src/scroll.rs index 19803b09..3942ca91 100644 --- a/goryak/src/scroll.rs +++ b/goryak/src/scroll.rs @@ -7,7 +7,7 @@ use yakui_core::widget::{EventContext, LayoutContext, PaintContext, Widget}; use yakui_core::Response; use yakui_widgets::shapes::RoundedRectangle; -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum VertScrollSize { /// The scroll size is a percentage of the parent's size. Percent(f32), @@ -249,3 +249,231 @@ impl Widget for VertScrollWidget { } } } + +#[derive(Debug, PartialEq)] +pub enum HorizScrollSize { + /// The scroll size is a percentage of the parent's size. + Percent(f32), + /// The scroll size is exactly what is given (within constraints). + Exact(f32), + /// The scroll size is at most what is given (within constraints). + Fixed(f32), + /// The scroll size is equal to the parent's size. + Max, +} + +impl HorizScrollSize { + pub fn show(self, children: F) -> Response { + yakui_widgets::util::widget_children::( + children, + HorizScroll { size: self }, + ) + } +} + +#[derive(Debug)] +pub struct HorizScroll { + pub size: HorizScrollSize, +} + +impl HorizScroll { + pub fn show(self, children: F) -> Response { + yakui_widgets::util::widget_children::(children, self) + } +} + +#[derive(Debug)] +#[non_exhaustive] +pub struct HorizScrollWidget { + props: HorizScroll, + scroll_position: Cell, + size: Cell, + canvas_size: Cell, + scrollbar_rect: Cell, + scrollbar_dragging: Cell>, + scrollbar_hovered: bool, +} + +pub type HorizScrollResponse = (); + +impl Widget for HorizScrollWidget { + type Props<'a> = HorizScroll; + type Response = HorizScrollResponse; + + fn new() -> Self { + Self { + props: HorizScroll { + size: HorizScrollSize::Max, + }, + scroll_position: Cell::new(0.0), + size: Cell::new(0.0), + canvas_size: Cell::new(0.0), + scrollbar_rect: Cell::new(Rect::ZERO), + scrollbar_dragging: Default::default(), + scrollbar_hovered: false, + } + } + + fn update(&mut self, props: Self::Props<'_>) -> Self::Response { + self.props = props; + } + + fn flex(&self) -> (u32, FlexFit) { + match self.props.size { + HorizScrollSize::Max => (1, FlexFit::Tight), + HorizScrollSize::Percent(_) => (1, FlexFit::Loose), + HorizScrollSize::Exact(_) => (0, FlexFit::Tight), + _ => (0, FlexFit::Loose), + } + } + + fn layout(&self, mut ctx: LayoutContext<'_>, mut constraints: Constraints) -> Vec2 { + if self.props.size != HorizScrollSize::Max { + ctx.layout.enable_clipping(ctx.dom); + } + + let node = ctx.dom.get_current(); + let mut canvas_size = Vec2::ZERO; + + let main_axis_size = match self.props.size { + HorizScrollSize::Max => constraints.max.x, + HorizScrollSize::Fixed(h) => { + constraints.max.x = constraints.max.x.min(h); + constraints.min.x + } + HorizScrollSize::Exact(h) => { + constraints.max.x = constraints.max.x.min(h); + constraints.min.x + } + HorizScrollSize::Percent(percent) => { + constraints.max.x *= percent; + constraints.min.x + } + }; + + canvas_size.x = main_axis_size; + + let child_constraints = Constraints { + min: Vec2::new(0.0, constraints.min.y), + max: Vec2::new(1000000.0, constraints.max.y), + }; + + for &child in &node.children { + let child_size = ctx.calculate_layout(child, child_constraints); + canvas_size = canvas_size.max(child_size); + } + + let mut size = constraints.constrain(canvas_size); + if let HorizScrollSize::Exact(_) = self.props.size { + size.x = size.x.max(constraints.max.x); + } + + self.canvas_size.set(canvas_size.x); + self.size.set(size.x); + + let max_scroll_position = (canvas_size.x - size.x).max(0.0); + let scroll_position = self.scroll_position.get().clamp(0.0, max_scroll_position); + + self.scroll_position.set(scroll_position); + + for &child in &node.children { + let off = Vec2::new(-scroll_position, 0.0); + ctx.layout.set_pos(child, off); + } + + size + } + + fn paint(&self, mut ctx: PaintContext<'_>) { + let drawn_rect = ctx.layout.get(ctx.dom.current()).unwrap().rect; + let node = ctx.dom.get_current(); + + for &child in &node.children { + ctx.paint(child); + } + + let mut scrollbar_height: f32 = 4.0; + if self.scrollbar_hovered { + scrollbar_height = 5.0; + } + const SCROLLBAR_PAD_Y: f32 = 4.0; + const SCROLLBAR_PAD_X: f32 = 2.0; + + if self.canvas_size.get() <= drawn_rect.size().x { + self.scrollbar_rect.set(Rect::ZERO); + return; + } + let scrollbar_progress = + self.scroll_position.get() / (self.canvas_size.get() - self.size.get()); + let scroll_bar_width = drawn_rect.size().x * (drawn_rect.size().x / self.canvas_size.get()); + let remaining_space = drawn_rect.size().x - scroll_bar_width - SCROLLBAR_PAD_X; + + let pos_x = remaining_space * scrollbar_progress + SCROLLBAR_PAD_X * 0.5; + + let scroll_bar_pos = drawn_rect.pos() + + Vec2::new( + pos_x, + drawn_rect.size().y - scrollbar_height * 0.5 - SCROLLBAR_PAD_Y, + ); + let scroll_bar_rect = Rect::from_pos_size( + scroll_bar_pos, + Vec2::new(scroll_bar_width, scrollbar_height), + ); + + self.scrollbar_rect.set(scroll_bar_rect); + + let mut paint_rect = RoundedRectangle::new(scroll_bar_rect, 5.0); + + let mut alpha = 0.5; + if self.scrollbar_hovered { + alpha = 0.7; + } + if self.scrollbar_dragging.get().is_some() { + alpha = 1.0; + } + + paint_rect.color = Color::WHITE.with_alpha(alpha); + paint_rect.add(ctx.paint); + } + + fn event_interest(&self) -> EventInterest { + EventInterest::MOUSE_ALL + } + + fn event(&mut self, _ctx: EventContext<'_>, event: &WidgetEvent) -> EventResponse { + match *event { + WidgetEvent::MouseScroll { delta } => { + *self.scroll_position.get_mut() += delta.y; + EventResponse::Sink + } + WidgetEvent::MouseMoved(Some(pos)) => { + if let Some(last_pos) = self.scrollbar_dragging.get_mut() { + *self.scroll_position.get_mut() += + (pos.x - *last_pos) * (*self.canvas_size.get_mut() / *self.size.get_mut()); + *last_pos = pos.x; + return EventResponse::Sink; + } else { + self.scrollbar_hovered = self.scrollbar_rect.get_mut().contains_point(pos); + } + EventResponse::Bubble + } + WidgetEvent::MouseButtonChanged { + position, + button: MouseButton::One, + down, + .. + } => { + if !down { + *self.scrollbar_dragging.get_mut() = None; + return EventResponse::Bubble; + } + if self.scrollbar_rect.get_mut().contains_point(position) { + *self.scrollbar_dragging.get_mut() = Some(position.x); + return EventResponse::Sink; + } + EventResponse::Bubble + } + _ => EventResponse::Bubble, + } + } +} diff --git a/native_app/src/gui/hud/toolbox/building.rs b/native_app/src/gui/hud/toolbox/building.rs index c09e4cf5..4f67a557 100644 --- a/native_app/src/gui/hud/toolbox/building.rs +++ b/native_app/src/gui/hud/toolbox/building.rs @@ -1,17 +1,18 @@ use common::FastMap; use engine::{Context, TextureBuilder}; -use yakui::widgets::List; +use yakui::widgets::{Layer, List}; use yakui::{ - reflow, use_state, Alignment, Color, CrossAxisAlignment, Dim2, MainAxisAlignment, Pivot, - TextureId, Vec2, + reflow, use_state, Alignment, Color, CrossAxisAlignment, Dim2, MainAxisAlignment, MainAxisSize, + Pivot, TextureId, Vec2, }; use crate::gui::item_icon_yakui; use engine::wgpu::TextureFormat; use geom::{Camera, Degrees, Polygon, Vec3}; use goryak::{ - blur_bg, fixed_spacer, image_button, is_hovered, mincolumn, minrow, on_secondary_container, - padxy, primary, secondary_container, textc, titlec, + blur_bg, debug_constraints, debug_size, fixed_spacer, image_button, is_hovered, mincolumn, + minrow, on_secondary_container, padxy, pady, primary, secondary_container, textc, titlec, + HorizScroll, HorizScrollSize, }; use prototypes::{ prototypes_iter, BuildingPrototypeID, GoodsCompanyID, GoodsCompanyPrototype, Prototype, @@ -29,150 +30,163 @@ pub fn special_building_properties(uiw: &UiWorld) { let mut state = uiw.write::(); let icons = uiw.read::(); - padxy(0.0, 10.0, || { - let mut l = List::row(); - l.main_axis_alignment = MainAxisAlignment::Center; - l.cross_axis_alignment = CrossAxisAlignment::Center; - l.item_spacing = 10.0; - l.show(|| { - let tooltip_active = use_state(|| Option::<(GoodsCompanyID, Instant)>::None); - for descr in prototypes_iter::() { - let Some(tex_id) = icons.ids.get(&descr.parent().id) else { - continue; - }; + HorizScrollSize::Max.show(|| { + padxy(50.0, 10.0, || { + let mut l = List::row(); + l.main_axis_alignment = MainAxisAlignment::Center; + l.cross_axis_alignment = CrossAxisAlignment::Center; + l.main_axis_size = MainAxisSize::Min; + l.item_spacing = 10.0; + l.show(|| { + let tooltip_active = use_state(|| Option::<(GoodsCompanyID, Instant)>::None); + for descr in prototypes_iter::() { + let Some(tex_id) = icons.ids.get(&descr.parent().id) else { + continue; + }; - minrow(0.0, || { - let default_col = Color::WHITE; - let hover_col = primary(); - let active_col = default_col.with_alpha(0.5); + minrow(0.0, || { + let default_col = Color::WHITE; + let hover_col = primary(); + let active_col = default_col.with_alpha(0.5); - let resp = image_button( - *tex_id, - Vec2::splat(64.0), - default_col, - hover_col, - active_col, - "", - ); - - if resp.hovering { - tooltip_active.set(Some((descr.id, Instant::now()))); - } + let resp = image_button( + *tex_id, + Vec2::splat(64.0), + default_col, + hover_col, + active_col, + "", + ); - if tooltip_active - .borrow() - .map(|(id, last)| id == descr.id && last.elapsed().as_secs_f32() < 0.2) - .unwrap_or(false) - { - reflow( - Alignment::TOP_CENTER, - Pivot::BOTTOM_CENTER, - Dim2::pixels(0.0, -20.0), - || { - let hov_resp = is_hovered(|| { - blur_bg(secondary_container().with_alpha(0.5), 10.0, || { - padxy(10.0, 10.0, || { - mincolumn(3.0, || { - titlec(on_secondary_container(), &descr.label); - textc( - on_secondary_container(), - format!("workers: {}", descr.n_workers), - ); + if resp.hovering { + tooltip_active.set(Some((descr.id, Instant::now()))); + } - if let Some(ref recipe) = descr.recipe { - fixed_spacer((0.0, 10.0)); - if !recipe.consumption.is_empty() { - textc( + if tooltip_active + .borrow() + .map(|(id, last)| id == descr.id && last.elapsed().as_secs_f32() < 0.2) + .unwrap_or(false) + { + reflow( + Alignment::TOP_CENTER, + Pivot::BOTTOM_CENTER, + Dim2::pixels(0.0, -20.0), + || { + let hov_resp = is_hovered(|| { + blur_bg( + secondary_container().with_alpha(0.5), + 10.0, + || { + padxy(10.0, 10.0, || { + mincolumn(3.0, || { + titlec( on_secondary_container(), - "consumption:", + &descr.label, ); - for item in &recipe.consumption { - item_icon_yakui( - uiw, - item.id, - item.amount, - ); - } - fixed_spacer((0.0, 10.0)); - } - if !recipe.production.is_empty() { textc( on_secondary_container(), - "production:", + format!("workers: {}", descr.n_workers), ); - for item in &recipe.production { - item_icon_yakui( - uiw, - item.id, - item.amount, + + if let Some(ref recipe) = descr.recipe { + fixed_spacer((0.0, 10.0)); + if !recipe.consumption.is_empty() { + textc( + on_secondary_container(), + "consumption:", + ); + for item in &recipe.consumption { + item_icon_yakui( + uiw, + item.id, + item.amount, + ); + } + fixed_spacer((0.0, 10.0)); + } + if !recipe.production.is_empty() { + textc( + on_secondary_container(), + "production:", + ); + for item in &recipe.production { + item_icon_yakui( + uiw, + item.id, + item.amount, + ); + } + fixed_spacer((0.0, 10.0)); + } + textc( + on_secondary_container(), + format!( + "time: {}", + recipe.duration + ), + ); + textc( + on_secondary_container(), + format!( + "storage multiplier: {}", + recipe.storage_multiplier + ), ); } - fixed_spacer((0.0, 10.0)); - } - textc( - on_secondary_container(), - format!("time: {}", recipe.duration), - ); - textc( - on_secondary_container(), - format!( - "storage multiplier: {}", - recipe.storage_multiplier - ), - ); - } - if let Some(p) = descr.power_consumption { - fixed_spacer((0.0, 10.0)); - textc( - on_secondary_container(), - format!("Power: {}", p), - ); - } - if let Some(p) = descr.power_production { - fixed_spacer((0.0, 10.0)); - textc( - on_secondary_container(), - format!("Power production: {}", p), - ); - } - }); - }); + if let Some(p) = descr.power_consumption { + fixed_spacer((0.0, 10.0)); + textc( + on_secondary_container(), + format!("Power: {}", p), + ); + } + if let Some(p) = descr.power_production { + fixed_spacer((0.0, 10.0)); + textc( + on_secondary_container(), + format!("Power production: {}", p), + ); + } + }); + }); + }, + ); }); - }); - if hov_resp.hovered { - tooltip_active.set(Some((descr.id, Instant::now()))); - } - }, - ); - } + if hov_resp.hovered { + tooltip_active.set(Some((descr.id, Instant::now()))); + } + }, + ); + } - if resp.clicked || state.opt.is_none() { - let bkind = BuildingKind::GoodsCompany(descr.id); - let bgen = descr.bgen; - let has_zone = descr.zone.is_some(); - state.opt = Some(SpecialBuildKind { - road_snap: true, - make: Box::new(move |args| { - vec![WorldCommand::MapBuildSpecialBuilding { - pos: args.obb, - kind: bkind, - gen: bgen, - zone: has_zone.then(|| { - Zone::new( - Polygon::from(args.obb.corners.as_slice()), - geom::Vec2::X, - ) - }), - connected_road: args.connected_road, - }] - }), - size: descr.size, - asset: descr.asset.clone(), - }); - } - }); - } + if resp.clicked || state.opt.is_none() { + let bkind = BuildingKind::GoodsCompany(descr.id); + let bgen = descr.bgen; + let has_zone = descr.zone.is_some(); + state.opt = Some(SpecialBuildKind { + road_snap: true, + make: Box::new(move |args| { + vec![WorldCommand::MapBuildSpecialBuilding { + pos: args.obb, + kind: bkind, + gen: bgen, + zone: has_zone.then(|| { + Zone::new( + Polygon::from(args.obb.corners.as_slice()), + geom::Vec2::X, + ) + }), + connected_road: args.connected_road, + }] + }), + size: descr.size, + asset: descr.asset.clone(), + }); + } + }); + } + }); }); });