diff --git a/src/draw.rs b/src/draw.rs index b593252..f4d2271 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -52,6 +52,13 @@ impl Rect { } } + pub fn from_triangle_bounds(tri: &ColoredTriangle) -> Self { + Self { + bl: tri.v1.min(tri.v2).min(tri.v3), + tr: tri.v1.max(tri.v2).max(tri.v3), + } + } + pub fn inset(&self, d: f32) -> Self { Self { bl: self.bl + d, @@ -74,10 +81,24 @@ impl Rect { } } - pub fn clip(&self, other: &Self) -> Self { - Self { + pub fn is_valid(&self) -> bool { + self.bl.cmplt(self.tr).all() + } + + pub fn intersects_rect(&self, other: &Self) -> bool { + self.bl.cmple(other.tr).all() && self.tr.cmpge(other.bl).all() + } + + pub fn intersection(&self, other: &Self) -> Option { + let clipped = Self { bl: self.bl.max(other.bl), tr: self.tr.min(other.tr), + }; + + if clipped.is_valid() { + Some(clipped) + } else { + None } } @@ -118,7 +139,20 @@ impl DrawContext { } } - pub fn draw_triangle(&self, mut v1: Vec2, mut v2: Vec2, mut v3: Vec2, mut color: Color) { + pub fn draw_triangle(&self, v1: Vec2, v2: Vec2, v3: Vec2, color: Color) { + if let Some(clip_rect) = self.clip_rect.as_ref() { + let tri = ColoredTriangle { v1, v2, v3, color }; + let bb = Rect::from_triangle_bounds(&tri); + + if clip_rect.contains_rect(&bb) { + self.draw_triangle_noclip(tri.v1, tri.v2, tri.v3, tri.color); + } + } else { + self.draw_triangle_noclip(v1, v2, v3, color); + } + } + + fn draw_triangle_noclip(&self, mut v1: Vec2, mut v2: Vec2, mut v3: Vec2, mut color: Color) { if let Some(offset) = self.offset { v1 += offset; v2 += offset; @@ -195,13 +229,23 @@ impl DrawContext { } pub fn draw_rect(&self, rect: Rect, color: Color) { + let rect = if let Some(clip_rect) = self.clip_rect.as_ref() { + if let Some(clipped) = clip_rect.intersection(&rect) { + clipped + } else { + return; + } + } else { + rect + }; + let v1 = rect.bl; let v2 = Vec2::new(rect.bl.x, rect.tr.y); let v3 = Vec2::new(rect.tr.x, rect.bl.y); let v4 = rect.tr; - self.draw_triangle(v1, v2, v3, color); - self.draw_triangle(v2, v3, v4, color); + self.draw_triangle_noclip(v1, v2, v3, color); + self.draw_triangle_noclip(v2, v3, v4, color); } pub fn draw_rounded_rect(&self, rect: Rect, radius: f32, color: Color) { @@ -272,17 +316,29 @@ impl DrawContext { &self.clip_rect } - pub fn with_offset(&self, mut offset: Vec2) -> Self { - if let Some(old) = self.offset { - offset += old; - } - + pub fn with_offset(&self, offset: Vec2) -> Self { Self { - offset: Some(offset), + offset: self.offset.map(|old| old + offset).or(Some(offset)), + clip_rect: self.clip_rect.map(|r| r.offset(-offset)), ..*self } } + pub fn with_clip_rect(&self, mut clip_rect: Rect) -> Option { + if let Some(old) = self.clip_rect { + if let Some(clipped) = old.intersection(&clip_rect) { + clip_rect = clipped; + } else { + return None; + } + } + + Some(Self { + clip_rect: Some(clip_rect), + ..*self + }) + } + pub fn with_opacity(&self, mut opacity: f32) -> Self { if let Some(old) = self.opacity { opacity *= old; diff --git a/src/lib.rs b/src/lib.rs index 55d8e72..3634c72 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,6 +52,12 @@ impl Color { pub const WHITE: Self = Self::new(1., 1., 1., 1.); pub const BLACK: Self = Self::new(0., 0., 0., 1.); pub const TRANSPARENT: Self = Self::new(0., 0., 0., 0.); + pub const RED: Self = Self::new(1., 0., 0., 1.); + pub const GREEN: Self = Self::new(1., 0., 0., 1.); + pub const BLUE: Self = Self::new(0., 1., 0., 1.); + pub const YELLOW: Self = Self::new(1., 1., 0., 1.); + pub const MAGENTA: Self = Self::new(1., 0., 1., 1.); + pub const CYAN: Self = Self::new(0., 1., 1., 1.); pub const fn new(r: f32, g: f32, b: f32, a: f32) -> Self { Self { r, g, b, a } diff --git a/src/widgets.rs b/src/widgets.rs index d577bff..76614cd 100644 --- a/src/widgets.rs +++ b/src/widgets.rs @@ -1,5 +1,5 @@ use crate::anim::Animation; -use crate::draw::{Corner, CornerFlags, DrawContext, Rect}; +use crate::draw::{CornerFlags, DrawContext, Rect}; use crate::{Color, CursorEventKind, Vec2}; use keyframe::functions::*; @@ -13,6 +13,10 @@ pub trait Button { fn was_clicked(&self) -> bool; } +pub trait FixedWidth { + fn get_width(&self) -> f32; +} + #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum ButtonState { Idle, @@ -214,6 +218,7 @@ impl Default for ScrollBarStyle { pub struct ScrollBar { height: f32, style: ScrollBarStyle, + is_dirty: bool, scroll: f32, content_height: f32, rail_rect: Rect, @@ -242,6 +247,7 @@ impl ScrollBar { Self { height, style, + is_dirty: true, scroll: 0.0, content_height, rail_rect, @@ -263,6 +269,10 @@ impl ScrollBar { Rect::from_xy_size(body_xy, body_size) } + pub fn is_dirty(&self) -> bool { + self.is_dirty + } + pub fn get_scroll(&self) -> f32 { self.scroll } @@ -270,6 +280,7 @@ impl ScrollBar { impl Widget for ScrollBar { fn update(&mut self, dt: f32) { + self.is_dirty = false; self.body_color_anim.update(dt); } @@ -310,6 +321,8 @@ impl Widget for ScrollBar { } else if self.scroll < 0.0 { self.scroll = 0.0; } + + self.is_dirty = true; } } CursorEventKind::Select => { @@ -335,6 +348,66 @@ impl Widget for ScrollBar { } } +pub struct ScrollView { + inner: Offset, + scroll_bar: Offset, + clip_rect: Rect, + height: f32, + content_height: f32, +} + +impl ScrollView { + pub fn new( + bar_style: ScrollBarStyle, + width: f32, + height: f32, + inner_cb: impl FnOnce(f32) -> (T, f32), + ) -> Self { + let content_width = width - bar_style.body_width - bar_style.margin.x * 2.0; + let (inner, content_height) = inner_cb(content_width); + let inner = Offset::new(inner, Vec2::ZERO); + + let scroll_bar_offset = Vec2::new(content_width, 0.0); + let scroll_bar = ScrollBar::new(height, content_height, bar_style); + let scroll_bar = Offset::new(scroll_bar, scroll_bar_offset); + + let clip_rect = Rect::from_xy_size(Vec2::ZERO, Vec2::new(content_width, height)); + + Self { + inner, + scroll_bar, + clip_rect, + content_height, + height, + } + } +} + +impl Widget for ScrollView { + fn update(&mut self, dt: f32) { + if self.scroll_bar.inner.is_dirty() { + let yoff = self.scroll_bar.inner.get_scroll() - self.content_height + self.height; + self.inner.set_offset(Vec2::new(0.0, yoff)); + } + + self.inner.update(dt); + self.scroll_bar.update(dt); + } + + fn draw(&mut self, ctx: &DrawContext) { + self.scroll_bar.draw(ctx); + + if let Some(ctx) = ctx.with_clip_rect(self.clip_rect) { + self.inner.draw(&ctx); + } + } + + fn on_cursor_event(&mut self, kind: CursorEventKind, at: Vec2) { + self.inner.on_cursor_event(kind, at); + self.scroll_bar.on_cursor_event(kind, at); + } +} + #[derive(Eq, PartialEq)] pub enum ScrollMenuState { Opening, @@ -549,6 +622,10 @@ impl Offset { pub fn new(inner: T, offset: Vec2) -> Self { Self { inner, offset } } + + pub fn set_offset(&mut self, offset: Vec2) { + self.offset = offset; + } } impl Widget for Offset { @@ -690,7 +767,7 @@ impl Widget for MainMenu { pub struct TabMenu { tabs: Vec, - scroll_bar: Offset, + view: Offset>, head_rect: Rect, separator_rect: Rect, } @@ -746,9 +823,23 @@ impl TabMenu { tr: Vec2::new(head_width, Self::HEAD_HEIGHT), }; + let view_rect = Rect { + bl: Vec2::new(Self::TAB_WIDTH + Self::SEPARATOR_WIDTH, -tab_list_height), + tr: Vec2::new(head_rect.tr.x, 0.0), + }; + + let view = ScrollView::new( + Default::default(), + view_rect.width(), + view_rect.height(), + |available_width: f32| Inventory::new(available_width), + ); + + let view = Offset::new(view, view_rect.bl); + Self { tabs, - scroll_bar, + view, separator_rect, head_rect, } @@ -761,7 +852,7 @@ impl Widget for TabMenu { tab.update(dt); } - self.scroll_bar.update(dt); + self.view.update(dt); } fn draw(&mut self, ctx: &DrawContext) { @@ -791,7 +882,7 @@ impl Widget for TabMenu { head_color, ); - self.scroll_bar.draw(ctx); + self.view.draw(ctx); } fn on_cursor_event(&mut self, kind: CursorEventKind, at: Vec2) { @@ -799,6 +890,49 @@ impl Widget for TabMenu { tab.on_cursor_event(kind, at); } - self.scroll_bar.on_cursor_event(kind, at); + self.view.on_cursor_event(kind, at); } } + +pub struct Inventory { + width: f32, + height: f32, +} + +impl Inventory { + pub fn new(available_width: f32) -> (Self, f32) { + let height = 1.5; + + ( + Self { + width: available_width, + height, + }, + height, + ) + } +} + +impl Widget for Inventory { + fn update(&mut self, dt: f32) {} + + fn draw(&mut self, ctx: &DrawContext) { + let box_size = 0.04; + let box_margin = 0.01; + let box_stride = box_size + box_margin; + + let grid_width = (self.width / box_stride).floor() as usize; + let grid_height = (self.height / box_stride).floor() as usize; + + for x in 0..grid_width { + for y in 0..grid_height { + let off = Vec2::new(x as f32, y as f32) * box_stride; + let rect = Rect::from_xy_size(off, Vec2::new(box_size, box_size)); + let color = Color::MAGENTA; + ctx.draw_rect(rect, color); + } + } + } + + fn on_cursor_event(&mut self, kind: CursorEventKind, at: Vec2) {} +}