From fd5785ce6d419f2b2a10827a5a4d5b17100a6ea8 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 5 Mar 2022 09:07:50 +0000 Subject: [PATCH 01/16] EditField: do not draw cursor if not editable --- crates/kas-widgets/src/edit_field.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/kas-widgets/src/edit_field.rs b/crates/kas-widgets/src/edit_field.rs index 4cf5d38c9..a6e61f473 100644 --- a/crates/kas-widgets/src/edit_field.rs +++ b/crates/kas-widgets/src/edit_field.rs @@ -436,7 +436,7 @@ widget! { class, ); } - if draw.ev_state().has_char_focus(self.id_ref()).0 { + if self.editable && draw.ev_state().has_char_focus(self.id_ref()).0 { draw.text_cursor( self.rect().pos, self.text.as_ref(), From 6df097b4cbbf920df87654ef6676f9d91eee72ff Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 5 Mar 2022 10:10:07 +0000 Subject: [PATCH 02/16] Set depress visual state for any popup parent --- crates/kas-core/src/event/manager/mgr_pub.rs | 6 ++++++ crates/kas-widgets/src/combobox.rs | 5 +---- crates/kas-widgets/src/menu/submenu.rs | 7 ------- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/crates/kas-core/src/event/manager/mgr_pub.rs b/crates/kas-core/src/event/manager/mgr_pub.rs index 5d994dc5c..a5e2acda2 100644 --- a/crates/kas-core/src/event/manager/mgr_pub.rs +++ b/crates/kas-core/src/event/manager/mgr_pub.rs @@ -83,6 +83,11 @@ impl EventState { return true; } } + for popup in &self.popups { + if *w_id == popup.1.parent { + return true; + } + } false } @@ -478,6 +483,7 @@ impl<'a> EventMgr<'a> { /// /// The parent of a popup automatically receives mouse-motion events /// ([`Event::PressMove`]) which may be used to navigate menus. + /// The parent automatically receives the "depressed" visual state. /// /// A pop-up may be closed by calling [`EventMgr::close_window`] with /// the [`WindowId`] returned by this method. diff --git a/crates/kas-widgets/src/combobox.rs b/crates/kas-widgets/src/combobox.rs index 1f4e498e7..2ad1ea80f 100644 --- a/crates/kas-widgets/src/combobox.rs +++ b/crates/kas-widgets/src/combobox.rs @@ -49,10 +49,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_core(self.core_data()); - if self.popup_id.is_some() { - draw.state.insert(InputState::DEPRESS); - } + let draw = draw.with_core(self.core_data()); self.layout().draw(draw); } } diff --git a/crates/kas-widgets/src/menu/submenu.rs b/crates/kas-widgets/src/menu/submenu.rs index 70650e807..89283afda 100644 --- a/crates/kas-widgets/src/menu/submenu.rs +++ b/crates/kas-widgets/src/menu/submenu.rs @@ -26,7 +26,6 @@ widget! { #[widget] pub list: PopupFrame>, popup_id: Option, - closing_menu: bool, } impl Self where D: Default { @@ -69,7 +68,6 @@ widget! { frame_store: Default::default(), list: PopupFrame::new(Column::new(list)), popup_id: None, - closing_menu: false, } } @@ -88,7 +86,6 @@ widget! { fn close_menu(&mut self, mgr: &mut EventMgr, restore_focus: bool) { if let Some(id) = self.popup_id { mgr.close_window(id, restore_focus); - self.closing_menu = true; } } @@ -147,9 +144,6 @@ widget! { fn draw(&mut self, mut draw: DrawMgr) { let mut draw = draw.with_core(self.core_data()); - if self.popup_id.is_some() && !self.closing_menu { - draw.state.insert(InputState::DEPRESS); - } draw.frame(self.core.rect, FrameStyle::MenuEntry); draw.text_accel( self.label_store.pos, @@ -174,7 +168,6 @@ widget! { Event::PopupRemoved(id) => { debug_assert_eq!(Some(id), self.popup_id); self.popup_id = None; - self.closing_menu = false; Response::Used } Event::Command(cmd, _) => self.handle_dir_key(mgr, cmd), From 87f93f4d94d822b3446ed32c4d0816b3331dcd84 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 5 Mar 2022 10:14:39 +0000 Subject: [PATCH 03/16] ComboBox: use accent colour when depressed OR focussed --- crates/kas-theme/src/colors.rs | 2 +- crates/kas-theme/src/flat_theme.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/kas-theme/src/colors.rs b/crates/kas-theme/src/colors.rs index 8991d51b1..76039f6b1 100644 --- a/crates/kas-theme/src/colors.rs +++ b/crates/kas-theme/src/colors.rs @@ -205,7 +205,7 @@ impl ColorsLinear { /// Get colour for navigation highlight region, if any pub fn nav_region(&self, state: InputState) -> Option { - if state.nav_focus() && !state.disabled() { + if state.depress() || state.nav_focus() && !state.disabled() { Some(self.nav_focus) } else { None diff --git a/crates/kas-theme/src/flat_theme.rs b/crates/kas-theme/src/flat_theme.rs index 50a6799a4..42249f90b 100644 --- a/crates/kas-theme/src/flat_theme.rs +++ b/crates/kas-theme/src/flat_theme.rs @@ -538,7 +538,7 @@ where fn button(&mut self, rect: Rect, col: Option, state: InputState) { let outer = Quad::conv(rect); - let col_bg = if state.nav_focus() && !state.disabled() { + let col_bg = if state.depress() || state.nav_focus() && !state.disabled() { self.cols.accent_soft } else { col.map(|c| c.into()).unwrap_or(self.cols.background) From ec8d2820548c8b7a0e5d3f54a9cb403832ddee91 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 5 Mar 2022 10:43:16 +0000 Subject: [PATCH 04/16] Move EventState into DrawHandle --- crates/kas-core/src/theme/draw.rs | 70 +++++++++-------------- crates/kas-theme/src/flat_theme.rs | 22 +++++-- crates/kas-theme/src/multi.rs | 6 +- crates/kas-theme/src/shaded_theme.rs | 23 ++++++-- crates/kas-theme/src/theme_dst.rs | 9 ++- crates/kas-theme/src/traits.rs | 9 ++- crates/kas-wgpu/src/window.rs | 14 +++-- crates/kas-widgets/src/menu/menu_entry.rs | 1 - crates/kas-widgets/src/menu/submenu.rs | 1 - examples/custom-theme.rs | 6 +- 10 files changed, 94 insertions(+), 67 deletions(-) diff --git a/crates/kas-core/src/theme/draw.rs b/crates/kas-core/src/theme/draw.rs index 0c59cf697..579658e80 100644 --- a/crates/kas-core/src/theme/draw.rs +++ b/crates/kas-core/src/theme/draw.rs @@ -25,7 +25,6 @@ use crate::{CoreData, TkAction}; /// Use [`DrawMgr::with_core`] to access draw methods. pub struct DrawMgr<'a> { h: &'a mut dyn DrawHandle, - ev: &'a mut EventState, disabled: bool, } @@ -33,30 +32,30 @@ impl<'a> DrawMgr<'a> { /// Construct from a [`DrawMgr`] and [`EventState`] #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] #[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] - pub fn new(h: &'a mut dyn DrawHandle, ev: &'a mut EventState, disabled: bool) -> Self { - DrawMgr { h, ev, disabled } + pub fn new(h: &'a mut dyn DrawHandle, disabled: bool) -> Self { + DrawMgr { h, disabled } } /// Access event-management state - pub fn ev_state(&self) -> &EventState { - self.ev + pub fn ev_state(&mut self) -> &EventState { + self.h.components().2 } /// Access a [`SizeMgr`] pub fn size_mgr(&mut self) -> SizeMgr { - SizeMgr::new(self.h.size_and_draw_shared().0) + SizeMgr::new(self.h.components().0) } /// Access a [`SetRectMgr`] pub fn set_rect_mgr T, T>(&mut self, mut f: F) -> T { - let (sh, ds) = self.h.size_and_draw_shared(); - let mut mgr = SetRectMgr::new(sh, ds, self.ev); + let (sh, ds, ev) = self.h.components(); + let mut mgr = SetRectMgr::new(sh, ds, ev); f(&mut mgr) } /// Access a [`DrawShared`] pub fn draw_shared(&mut self) -> &mut dyn DrawShared { - self.h.size_and_draw_shared().1 + self.h.components().1 } /// Access the low-level draw device @@ -70,10 +69,10 @@ impl<'a> DrawMgr<'a> { /// Add context to allow draw operations pub fn with_core<'b>(&'b mut self, core: &CoreData) -> DrawCtx<'b> { - let state = self.ev.draw_state(core, self.disabled); - let (h, ev) = (&mut *self.h, &mut *self.ev); + let state = self.h.components().2.draw_state(core, self.disabled); + let h = &mut *self.h; let wid = core.id.as_u64(); - DrawCtx { h, ev, wid, state } + DrawCtx { h, wid, state } } } @@ -86,7 +85,6 @@ impl<'a> DrawMgr<'a> { /// obtained through a [`SizeMgr`], e.g. through [`Self::size_mgr`]. pub struct DrawCtx<'a> { h: &'a mut dyn DrawHandle, - ev: &'a mut EventState, wid: u64, /// Input state for drawn objects /// @@ -107,7 +105,6 @@ impl<'a> DrawCtx<'a> { { DrawMgr { h: self.h, - ev: self.ev, disabled: self.state.contains(InputState::DISABLED), } } @@ -123,32 +120,31 @@ impl<'a> DrawCtx<'a> { { DrawCtx { h: self.h, - ev: self.ev, wid: self.wid, state: self.state, } } /// Access event-management state - pub fn ev_state(&self) -> &EventState { - self.ev + pub fn ev_state(&mut self) -> &EventState { + self.h.components().2 } /// Access a [`SizeMgr`] pub fn size_mgr(&mut self) -> SizeMgr { - SizeMgr::new(self.h.size_and_draw_shared().0) + SizeMgr::new(self.h.components().0) } /// Access a [`SetRectMgr`] pub fn set_rect_mgr T, T>(&mut self, mut f: F) -> T { - let (sh, ds) = self.h.size_and_draw_shared(); - let mut mgr = SetRectMgr::new(sh, ds, self.ev); + let (sh, ds, ev) = self.h.components(); + let mut mgr = SetRectMgr::new(sh, ds, ev); f(&mut mgr) } /// Access a [`DrawShared`] pub fn draw_shared(&mut self) -> &mut dyn DrawShared { - self.h.size_and_draw_shared().1 + self.h.components().1 } /// Access the low-level draw device @@ -165,11 +161,10 @@ impl<'a> DrawCtx<'a> { /// Adds a new draw pass of type [`PassType::Clip`], with draw operations /// clipped to `rect` and translated by `offset. pub fn with_clip_region(&mut self, rect: Rect, offset: Offset, mut f: F) { - let ev = &mut *self.ev; let wid = self.wid; let state = self.state; self.h.new_pass(rect, offset, PassType::Clip, &mut |h| { - f(DrawCtx { h, ev, wid, state }) + f(DrawCtx { h, wid, state }) }); } @@ -182,12 +177,11 @@ impl<'a> DrawCtx<'a> { /// a frame or shadow around this overlay, thus the /// [`DrawCtx::get_clip_rect`] may be larger than expected. pub fn with_overlay(&mut self, rect: Rect, mut f: F) { - let ev = &mut *self.ev; let wid = self.wid; let state = self.state; self.h .new_pass(rect, Offset::ZERO, PassType::Overlay, &mut |h| { - f(DrawCtx { h, ev, wid, state }) + f(DrawCtx { h, wid, state }) }); } @@ -250,14 +244,8 @@ impl<'a> DrawCtx<'a> { /// /// [`SizeMgr::text_bound`] should be called prior to this method to /// select a font, font size and wrap options (based on the [`TextClass`]). - pub fn text_accel( - &mut self, - pos: Coord, - text: &Text, - accel: bool, - class: TextClass, - ) { - self.h.text_accel(pos, text, accel, class, self.state); + pub fn text_accel(&mut self, pos: Coord, text: &Text, class: TextClass) { + self.h.text_accel(pos, text, class, self.state); } /// Draw some text using the standard font, with a subset selected @@ -358,14 +346,14 @@ impl<'a> DrawCtx<'a> { impl<'a> std::ops::BitOrAssign for DrawMgr<'a> { #[inline] fn bitor_assign(&mut self, action: TkAction) { - self.ev.send_action(action); + self.h.components().2.send_action(action); } } impl<'a> std::ops::BitOrAssign for DrawCtx<'a> { #[inline] fn bitor_assign(&mut self, action: TkAction) { - self.ev.send_action(action); + self.h.components().2.send_action(action); } } @@ -373,8 +361,8 @@ impl<'a> std::ops::BitOrAssign for DrawCtx<'a> { #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] #[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] pub trait DrawHandle { - /// Access a [`SizeHandle`] and a [`DrawShared`] - fn size_and_draw_shared(&mut self) -> (&dyn SizeHandle, &mut dyn DrawShared); + /// Access components: [`SizeHandle`], [`DrawShared`], [`EventState`] + fn components(&mut self) -> (&dyn SizeHandle, &mut dyn DrawShared, &mut EventState); /// Access the low-level draw device /// @@ -441,7 +429,6 @@ pub trait DrawHandle { &mut self, pos: Coord, text: &Text, - accel: bool, class: TextClass, state: InputState, ); @@ -518,8 +505,8 @@ pub trait DrawHandle { macro_rules! impl_ { (($($args:tt)*) DrawHandle for $ty:ty) => { impl<$($args)*> DrawHandle for $ty { - fn size_and_draw_shared(&mut self) -> (&dyn SizeHandle, &mut dyn DrawShared) { - self.deref_mut().size_and_draw_shared() + fn components(&mut self) -> (&dyn SizeHandle, &mut dyn DrawShared, &mut EventState) { + self.deref_mut().components() } fn draw_device(&mut self) -> &mut dyn Draw { self.deref_mut().draw_device() @@ -561,11 +548,10 @@ macro_rules! impl_ { &mut self, pos: Coord, text: &Text, - accel: bool, class: TextClass, state: InputState, ) { - self.deref_mut().text_accel(pos, text, accel, class, state); + self.deref_mut().text_accel(pos, text, class, state); } fn text_selected_range( &mut self, diff --git a/crates/kas-theme/src/flat_theme.rs b/crates/kas-theme/src/flat_theme.rs index 42249f90b..35e5a4370 100644 --- a/crates/kas-theme/src/flat_theme.rs +++ b/crates/kas-theme/src/flat_theme.rs @@ -16,6 +16,7 @@ use crate::{dim, ColorsLinear, Config, Theme}; use kas::cast::traits::*; use kas::dir::{Direction, Directional}; use kas::draw::{color::Rgba, *}; +use kas::event::EventState; use kas::geom::*; use kas::text::format::FormattableText; use kas::text::{fonts, AccelString, Effect, Text, TextApi, TextDisplay}; @@ -125,6 +126,7 @@ const DARK_SHADOW_OFFSET: Vec2 = Vec2::ZERO; pub struct DrawHandle<'a, DS: DrawSharedImpl> { pub(crate) draw: DrawIface<'a, DS>, + pub(crate) ev: &'a mut EventState, pub(crate) w: &'a mut dim::Window, pub(crate) cols: &'a ColorsLinear, } @@ -176,7 +178,12 @@ where } #[cfg(not(feature = "gat"))] - unsafe fn draw_handle(&self, draw: DrawIface, w: &mut Self::Window) -> Self::DrawHandle { + unsafe fn draw_handle( + &self, + draw: DrawIface, + ev: &mut EventState, + w: &mut Self::Window, + ) -> Self::DrawHandle { w.anim.update(); unsafe fn extend_lifetime<'b, T: ?Sized>(r: &'b T) -> &'static T { @@ -191,6 +198,7 @@ where shared: extend_lifetime_mut(draw.shared), pass: draw.pass, }, + ev: extend_lifetime_mut(ev), w: extend_lifetime_mut(w), cols: extend_lifetime(&self.cols), } @@ -199,12 +207,14 @@ where fn draw_handle<'a>( &'a self, draw: DrawIface<'a, DS>, + ev: &mut EventState, w: &'a mut Self::Window, ) -> Self::DrawHandle<'a> { w.anim.update(); DrawHandle { draw, + ev, w, cols: &self.cols, } @@ -319,8 +329,8 @@ impl<'a, DS: DrawSharedImpl> theme::DrawHandle for DrawHandle<'a, DS> where DS::Draw: DrawRoundedImpl, { - fn size_and_draw_shared(&mut self) -> (&dyn SizeHandle, &mut dyn DrawShared) { - (self.w, self.draw.shared) + fn components(&mut self) -> (&dyn SizeHandle, &mut dyn DrawShared, &mut EventState) { + (self.w, self.draw.shared, self.ev) } fn draw_device(&mut self) -> &mut dyn Draw { @@ -353,8 +363,9 @@ where } let mut handle = DrawHandle { - w: self.w, draw, + ev: self.ev, + w: self.w, cols: self.cols, }; f(&mut handle); @@ -440,7 +451,6 @@ where &mut self, pos: Coord, text: &Text, - accel: bool, _: TextClass, state: InputState, ) { @@ -450,7 +460,7 @@ where } else { self.cols.text }; - if accel { + if self.ev.show_accel_labels() { let effects = text.text().effect_tokens(); self.draw.text_col_effects(pos, text.as_ref(), col, effects); } else { diff --git a/crates/kas-theme/src/multi.rs b/crates/kas-theme/src/multi.rs index 903901cf8..1f9a6907c 100644 --- a/crates/kas-theme/src/multi.rs +++ b/crates/kas-theme/src/multi.rs @@ -11,6 +11,7 @@ use std::marker::Unsize; use crate::{Config, StackDst, Theme, ThemeDst, Window}; use kas::draw::{color, DrawIface, DrawSharedImpl, SharedState}; +use kas::event::EventState; use kas::theme::{DrawHandle, ThemeControl}; use kas::TkAction; @@ -150,6 +151,7 @@ impl Theme for MultiTheme { unsafe fn draw_handle( &self, draw: DrawIface, + ev: &mut EventState, window: &mut Self::Window, ) -> StackDst { unsafe fn extend_lifetime_mut<'b, T: ?Sized>(r: &'b mut T) -> &'static mut T { @@ -161,6 +163,7 @@ impl Theme for MultiTheme { shared: extend_lifetime_mut(draw.shared), pass: draw.pass, }, + extend_lifetime_mut(ev), extend_lifetime_mut(window), ) } @@ -169,9 +172,10 @@ impl Theme for MultiTheme { fn draw_handle<'a>( &'a self, draw: DrawIface<'a, DS>, + ev: &mut EventState, window: &'a mut Self::Window, ) -> StackDst { - self.themes[self.active].draw_handle(draw, window) + self.themes[self.active].draw_handle(draw, ev, window) } fn clear_color(&self) -> color::Rgba { diff --git a/crates/kas-theme/src/shaded_theme.rs b/crates/kas-theme/src/shaded_theme.rs index e44af9b83..51f4c1d6b 100644 --- a/crates/kas-theme/src/shaded_theme.rs +++ b/crates/kas-theme/src/shaded_theme.rs @@ -13,6 +13,7 @@ use crate::{DrawShaded, DrawShadedImpl}; use kas::cast::traits::*; use kas::dir::{Direction, Directional}; use kas::draw::{color::Rgba, *}; +use kas::event::EventState; use kas::geom::*; use kas::text::{AccelString, Text, TextApi, TextDisplay}; use kas::theme::{self, InputState, SizeHandle, ThemeControl}; @@ -78,6 +79,7 @@ const DIMS: dim::Parameters = dim::Parameters { pub struct DrawHandle<'a, DS: DrawSharedImpl> { draw: DrawIface<'a, DS>, + ev: &'a mut EventState, w: &'a mut dim::Window, cols: &'a ColorsLinear, } @@ -116,7 +118,12 @@ where } #[cfg(not(feature = "gat"))] - unsafe fn draw_handle(&self, draw: DrawIface, w: &mut Self::Window) -> Self::DrawHandle { + unsafe fn draw_handle( + &self, + draw: DrawIface, + ev: &mut EventState, + w: &mut Self::Window, + ) -> Self::DrawHandle { w.anim.update(); unsafe fn extend_lifetime<'b, T: ?Sized>(r: &'b T) -> &'static T { @@ -131,6 +138,7 @@ where shared: extend_lifetime_mut(draw.shared), pass: draw.pass, }, + ev: extend_lifetime_mut(ev), w: extend_lifetime_mut(w), cols: extend_lifetime(&self.flat.cols), } @@ -139,12 +147,14 @@ where fn draw_handle<'a>( &'a self, draw: DrawIface<'a, DS>, + ev: &mut EventState, w: &'a mut Self::Window, ) -> Self::DrawHandle<'a> { w.anim.update(); DrawHandle { draw, + ev, w, cols: &self.flat.cols, } @@ -181,6 +191,7 @@ where { super::flat_theme::DrawHandle { draw: self.draw.re(), + ev: self.ev, w: self.w, cols: self.cols, } @@ -229,8 +240,8 @@ impl<'a, DS: DrawSharedImpl> theme::DrawHandle for DrawHandle<'a, DS> where DS::Draw: DrawRoundedImpl + DrawShadedImpl, { - fn size_and_draw_shared(&mut self) -> (&dyn SizeHandle, &mut dyn DrawShared) { - (self.w, self.draw.shared) + fn components(&mut self) -> (&dyn SizeHandle, &mut dyn DrawShared, &mut EventState) { + (self.w, self.draw.shared, self.ev) } fn draw_device(&mut self) -> &mut dyn Draw { @@ -263,8 +274,9 @@ where } let mut handle = DrawHandle { - w: self.w, draw, + ev: self.ev, + w: self.w, cols: self.cols, }; f(&mut handle); @@ -333,11 +345,10 @@ where &mut self, pos: Coord, text: &Text, - accel: bool, class: TextClass, state: InputState, ) { - self.as_flat().text_accel(pos, text, accel, class, state); + self.as_flat().text_accel(pos, text, class, state); } fn text_selected_range( diff --git a/crates/kas-theme/src/theme_dst.rs b/crates/kas-theme/src/theme_dst.rs index 678f8c40a..1e6a67cc8 100644 --- a/crates/kas-theme/src/theme_dst.rs +++ b/crates/kas-theme/src/theme_dst.rs @@ -11,6 +11,7 @@ use std::ops::{Deref, DerefMut}; use super::{StackDst, Theme, Window}; use kas::draw::{color, DrawIface, DrawSharedImpl, SharedState}; +use kas::event::EventState; use kas::theme::{DrawHandle, SizeHandle, ThemeControl}; use kas::TkAction; @@ -75,6 +76,7 @@ pub trait ThemeDst: ThemeControl { unsafe fn draw_handle( &self, draw: DrawIface, + ev: &mut EventState, window: &mut dyn Window, ) -> StackDst; @@ -87,6 +89,7 @@ pub trait ThemeDst: ThemeControl { fn draw_handle<'a>( &'a self, draw: DrawIface<'a, DS>, + ev: &mut EventState, window: &'a mut dyn Window, ) -> StackDst; @@ -141,10 +144,11 @@ where unsafe fn draw_handle( &self, draw: DrawIface, + ev: &mut EventState, window: &mut dyn Window, ) -> StackDst { let window = window.as_any_mut().downcast_mut().unwrap(); - let h = >::draw_handle(self, draw, window); + let h = >::draw_handle(self, draw, ev, window); #[cfg(feature = "unsize")] { StackDst::new_or_boxed(h) @@ -192,10 +196,11 @@ impl<'a, DS: DrawSharedImpl, T: Theme> ThemeDst for T { fn draw_handle<'b>( &'b self, draw: DrawIface<'b, DS>, + ev: &mut EventState, window: &'b mut dyn Window, ) -> StackDst { let window = window.as_any_mut().downcast_mut().unwrap(); - let h = >::draw_handle(self, draw, window); + let h = >::draw_handle(self, draw, ev, window); StackDst::new_or_boxed(h) } diff --git a/crates/kas-theme/src/traits.rs b/crates/kas-theme/src/traits.rs index 780e94a32..3f1718d3d 100644 --- a/crates/kas-theme/src/traits.rs +++ b/crates/kas-theme/src/traits.rs @@ -6,6 +6,7 @@ //! Theme traits use kas::draw::{color, DrawIface, DrawSharedImpl, SharedState}; +use kas::event::EventState; use kas::theme::{DrawHandle, SizeHandle, ThemeControl}; use kas::TkAction; use std::any::Any; @@ -112,12 +113,14 @@ pub trait Theme: ThemeControl { unsafe fn draw_handle( &self, draw: DrawIface, + ev: &mut EventState, window: &mut Self::Window, ) -> Self::DrawHandle; #[cfg(feature = "gat")] fn draw_handle<'a>( &'a self, draw: DrawIface<'a, DS>, + ev: &mut EventState, window: &'a mut Self::Window, ) -> Self::DrawHandle<'a>; @@ -172,17 +175,19 @@ impl, DS: DrawSharedImpl> Theme for Box { unsafe fn draw_handle( &self, draw: DrawIface, + ev: &mut EventState, window: &mut Self::Window, ) -> Self::DrawHandle { - self.deref().draw_handle(draw, window) + self.deref().draw_handle(draw, ev, window) } #[cfg(feature = "gat")] fn draw_handle<'a>( &'a self, draw: DrawIface<'a, DS>, + ev: &mut EventState, window: &'a mut Self::Window, ) -> Self::DrawHandle<'a> { - self.deref().draw_handle(draw, window) + self.deref().draw_handle(draw, ev, window) } fn clear_color(&self) -> color::Rgba { diff --git a/crates/kas-wgpu/src/window.rs b/crates/kas-wgpu/src/window.rs index 48f4013a2..15b5d73c1 100644 --- a/crates/kas-wgpu/src/window.rs +++ b/crates/kas-wgpu/src/window.rs @@ -352,14 +352,20 @@ impl>> Window { #[cfg(not(feature = "gat"))] unsafe { // Safety: lifetimes do not escape the returned draw_handle value. - let mut draw_handle = shared.theme.draw_handle(draw, &mut self.theme_window); - let draw_mgr = DrawMgr::new(&mut draw_handle, &mut self.ev_state, false); + let mut draw_handle = + shared + .theme + .draw_handle(draw, &mut self.ev_state, &mut self.theme_window); + let draw_mgr = DrawMgr::new(&mut draw_handle, false); self.widget.draw(draw_mgr); } #[cfg(feature = "gat")] { - let mut draw_handle = shared.theme.draw_handle(draw, &mut self.theme_window); - let draw_mgr = DrawMgr::new(&mut draw_handle, &mut self.ev_state, false); + let mut draw_handle = + shared + .theme + .draw_handle(draw, &mut self.ev_state, &mut self.theme_window); + let draw_mgr = DrawMgr::new(&mut draw_handle, false); self.widget.draw(draw_mgr); } } diff --git a/crates/kas-widgets/src/menu/menu_entry.rs b/crates/kas-widgets/src/menu/menu_entry.rs index 8fc8aa29a..cc6ea631d 100644 --- a/crates/kas-widgets/src/menu/menu_entry.rs +++ b/crates/kas-widgets/src/menu/menu_entry.rs @@ -45,7 +45,6 @@ widget! { draw.text_accel( self.layout_label.pos, &self.label, - draw.ev_state().show_accel_labels(), TextClass::MenuLabel, ); } diff --git a/crates/kas-widgets/src/menu/submenu.rs b/crates/kas-widgets/src/menu/submenu.rs index 89283afda..166ffdd8e 100644 --- a/crates/kas-widgets/src/menu/submenu.rs +++ b/crates/kas-widgets/src/menu/submenu.rs @@ -148,7 +148,6 @@ widget! { draw.text_accel( self.label_store.pos, &self.label, - draw.ev_state().show_accel_labels(), TextClass::MenuLabel, ); } diff --git a/examples/custom-theme.rs b/examples/custom-theme.rs index ac2daa80d..06002f6cd 100644 --- a/examples/custom-theme.rs +++ b/examples/custom-theme.rs @@ -65,17 +65,19 @@ where unsafe fn draw_handle( &self, draw: DrawIface, + ev: &mut EventState, window: &mut Self::Window, ) -> Self::DrawHandle { - Theme::::draw_handle(&self.inner, draw, window) + Theme::::draw_handle(&self.inner, draw, ev, window) } #[cfg(feature = "gat")] fn draw_handle<'a>( &'a self, draw: DrawIface<'a, DS>, + ev: &mut EvState, window: &'a mut Self::Window, ) -> Self::DrawHandle<'a> { - Theme::::draw_handle(&self.inner, draw, window) + Theme::::draw_handle(&self.inner, draw, ev, window) } fn clear_color(&self) -> Rgba { From c7f8f9f0c5221ec100168bdf5377f1986c9dcaab Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 5 Mar 2022 16:03:24 +0000 Subject: [PATCH 05/16] Add CheckBox::editable state --- crates/kas-widgets/src/checkbox.rs | 61 ++++++++++++++++++++++----- crates/kas-widgets/src/edit_field.rs | 12 +++--- crates/kas-widgets/src/view/driver.rs | 4 +- examples/calculator.rs | 2 +- 4 files changed, 60 insertions(+), 19 deletions(-) diff --git a/crates/kas-widgets/src/checkbox.rs b/crates/kas-widgets/src/checkbox.rs index 50678fe62..abb52db64 100644 --- a/crates/kas-widgets/src/checkbox.rs +++ b/crates/kas-widgets/src/checkbox.rs @@ -21,6 +21,7 @@ widget! { #[widget_core] core: CoreData, state: bool, + editable: bool, on_toggle: Option Option>>, } @@ -51,6 +52,7 @@ widget! { CheckBoxBare { core: Default::default(), state: false, + editable: true, on_toggle: None, } } @@ -69,6 +71,7 @@ widget! { CheckBoxBare { core: self.core, state: self.state, + editable: self.editable, on_toggle: Some(Rc::new(f)), } } @@ -87,9 +90,7 @@ widget! { { CheckBoxBare::new().on_toggle(f) } - } - impl Self { /// Set the initial state of the checkbox. #[inline] #[must_use] @@ -97,6 +98,26 @@ widget! { self.state = state; self } + + /// Set whether this widget is editable (inline) + #[inline] + #[must_use] + pub fn with_editable(mut self, editable: bool) -> Self { + self.editable = editable; + self + } + + /// Get whether this widget is editable + #[inline] + pub fn is_editable(&self) -> bool { + self.editable + } + + /// Set whether this widget is editable + #[inline] + pub fn set_editable(&mut self, editable: bool) { + self.editable = editable; + } } impl HasBool for Self { @@ -120,7 +141,7 @@ widget! { fn handle(&mut self, mgr: &mut EventMgr, event: Event) -> Response { match event { - Event::Activate => { + Event::Activate if self.editable => { self.state = !self.state; mgr.redraw(self.id()); Response::update_or_msg(self.on_toggle.as_ref().and_then(|f| f(mgr, self.state))) @@ -134,24 +155,24 @@ widget! { widget! { /// A checkbox with label #[autoimpl(Debug)] - #[autoimpl(HasBool on self.checkbox)] + #[autoimpl(HasBool on self.inner)] #[derive(Clone, Default)] #[widget{ layout = row: *; - find_id = Some(self.checkbox.id()); + find_id = Some(self.inner.id()); }] pub struct CheckBox { #[widget_core] core: CoreData, #[widget] - checkbox: CheckBoxBare, + inner: CheckBoxBare, #[widget] label: AccelLabel, } impl WidgetConfig for Self { fn configure(&mut self, mgr: &mut SetRectMgr) { - mgr.add_accel_keys(self.checkbox.id_ref(), self.label.keys()); + mgr.add_accel_keys(self.inner.id_ref(), self.label.keys()); } } @@ -168,7 +189,7 @@ widget! { pub fn new>(label: T) -> Self { CheckBox { core: Default::default(), - checkbox: CheckBoxBare::new(), + inner: CheckBoxBare::new(), label: AccelLabel::new(label.into()), } } @@ -186,7 +207,7 @@ widget! { { CheckBox { core: self.core, - checkbox: self.checkbox.on_toggle(f), + inner: self.inner.on_toggle(f), label: self.label, } } @@ -213,8 +234,28 @@ widget! { #[inline] #[must_use] pub fn with_state(mut self, state: bool) -> Self { - self.checkbox = self.checkbox.with_state(state); + self.inner = self.inner.with_state(state); + self + } + + /// Set whether this widget is editable (inline) + #[inline] + #[must_use] + pub fn editable(mut self, editable: bool) -> Self { + self.inner = self.inner.with_editable(editable); self } + + /// Get whether this widget is editable + #[inline] + pub fn is_editable(&self) -> bool { + self.inner.is_editable() + } + + /// Set whether this widget is editable + #[inline] + pub fn set_editable(&mut self, editable: bool) { + self.inner.set_editable(editable); + } } } diff --git a/crates/kas-widgets/src/edit_field.rs b/crates/kas-widgets/src/edit_field.rs index a6e61f473..bad278cf4 100644 --- a/crates/kas-widgets/src/edit_field.rs +++ b/crates/kas-widgets/src/edit_field.rs @@ -292,21 +292,21 @@ impl EditBox<()> { } impl EditBox { - /// Set whether this `EditBox` is editable (inline) + /// Set whether this widget is editable (inline) #[inline] #[must_use] - pub fn editable(mut self, editable: bool) -> Self { - self.inner = self.inner.editable(editable); + pub fn with_editable(mut self, editable: bool) -> Self { + self.inner = self.inner.with_editable(editable); self } - /// Get whether this `EditBox` is editable + /// Get whether this widget is editable #[inline] pub fn is_editable(&self) -> bool { self.inner.is_editable() } - /// Set whether this `EditBox` is editable + /// Set whether this widget is editable #[inline] pub fn set_editable(&mut self, editable: bool) { self.inner.set_editable(editable); @@ -714,7 +714,7 @@ impl EditField { /// Set whether this `EditField` is editable (inline) #[inline] #[must_use] - pub fn editable(mut self, editable: bool) -> Self { + pub fn with_editable(mut self, editable: bool) -> Self { self.editable = editable; self } diff --git a/crates/kas-widgets/src/view/driver.rs b/crates/kas-widgets/src/view/driver.rs index 83a34f155..8fec7ec0d 100644 --- a/crates/kas-widgets/src/view/driver.rs +++ b/crates/kas-widgets/src/view/driver.rs @@ -118,7 +118,7 @@ impl Driver for Default { type Msg = VoidMsg; type Widget = CheckBoxBare; fn make(&self) -> Self::Widget { - CheckBoxBare::new().with_disabled(true) + CheckBoxBare::new().with_editable(false) } fn set(&self, widget: &mut Self::Widget, data: bool) -> TkAction { widget.set_bool(data) @@ -132,7 +132,7 @@ impl Driver for DefaultNav { type Msg = VoidMsg; type Widget = CheckBoxBare; fn make(&self) -> Self::Widget { - CheckBoxBare::new().with_disabled(true) + CheckBoxBare::new().with_editable(false) } fn set(&self, widget: &mut Self::Widget, data: bool) -> TkAction { widget.set_bool(data) diff --git a/examples/calculator.rs b/examples/calculator.rs index c19f66b5e..12405d324 100644 --- a/examples/calculator.rs +++ b/examples/calculator.rs @@ -77,7 +77,7 @@ fn main() -> kas::shell::Result<()> { }] #[handler(msg = VoidMsg)] struct { - #[widget] display: impl HasString = EditBox::new("0").editable(false).multi_line(true), + #[widget] display: impl HasString = EditBox::new("0").with_editable(false).multi_line(true), #[widget(use_msg = handle_button)] buttons -> Key = buttons, calc: Calculator = Calculator::new(), } From a6c4fe4b0eab8c8a9d06e1e594c1c58c62ad501c Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 5 Mar 2022 16:56:23 +0000 Subject: [PATCH 06/16] Add WidgetId::parent --- crates/kas-core/src/core/widget_id.rs | 63 +++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/crates/kas-core/src/core/widget_id.rs b/crates/kas-core/src/core/widget_id.rs index 85f6cb560..358238dc8 100644 --- a/crates/kas-core/src/core/widget_id.rs +++ b/crates/kas-core/src/core/widget_id.rs @@ -408,6 +408,42 @@ impl WidgetId { } } + /// Get the parent widget's identifier, if not root + /// + /// Note: there is no guarantee that [`Self::as_u64`] on the result will + /// match that of the original parent identifier. + pub fn parent(&self) -> Option { + match self.0.get() { + Variant::Invalid => None, + Variant::Int(x) => { + let mut bit_len = 4 * block_len(x); + while bit_len > 0 { + bit_len -= 4; + if bit_len > 0 && (x >> (64 - bit_len)) & 8 != 0 { + continue; + } + + let len = ((bit_len / 4) as u64) << SHIFT_LEN; + let mask = MASK_BITS << (56 - bit_len); + let id = (x & mask) | len | USE_BITS; + return Some(WidgetId(IntOrPtr::new_int(id))); + } + None + } + Variant::Slice(path) => { + let len = path.len(); + if len > 1 { + // TODO(opt): in some cases we could make Variant::Int + Some(WidgetId(IntOrPtr::new_iter( + path[0..len - 1].iter().cloned(), + ))) + } else { + None + } + } + } + } + /// Make an identifier for the child with the given `key` /// /// Note: this is not a getter method. Calling multiple times with the same @@ -705,6 +741,33 @@ mod test { assert_eq!(as_vec(0x1A93_007F_0000_0071), vec![1, 139, 0, 0, 7]); } + #[test] + fn test_parent() { + fn test(seq: &[usize]) { + println!("seq: {seq:?}"); + let mut id = WidgetId::ROOT; + let len = seq.len(); + for key in &seq[..len - 1] { + id = id.make_child(*key); + } + + if len == 0 { + assert_eq!(id.parent(), None); + } else { + let id2 = id.make_child(seq[len - 1]); + assert_eq!(id2.parent(), Some(id)); + } + } + + test(&[4]); + test(&[4, 0]); + test(&[0, 0, 0]); + test(&[0, 1, 0]); + test(&[9, 0, 1, 300]); + test(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0]); + test(&[313553, 13513, 13511631]); + } + #[test] fn test_make_child() { fn test(seq: &[usize], x: u64) { From 425a58e822097e86793b8079dd96eef764fb2467 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 5 Mar 2022 17:28:34 +0000 Subject: [PATCH 07/16] Unhandled events from children trigger main handler Exception: List/MatrixView have special behaviour only available when the child is not disabled. --- crates/kas-core/src/event/handler.rs | 9 ++++-- crates/kas-widgets/src/combobox.rs | 36 ++++++++++------------ crates/kas-widgets/src/menu/submenu.rs | 18 ++++------- crates/kas-widgets/src/view/list_view.rs | 9 ++++++ crates/kas-widgets/src/view/matrix_view.rs | 9 ++++++ 5 files changed, 48 insertions(+), 33 deletions(-) diff --git a/crates/kas-core/src/event/handler.rs b/crates/kas-core/src/event/handler.rs index 83c06aceb..4e996f9f7 100644 --- a/crates/kas-core/src/event/handler.rs +++ b/crates/kas-core/src/event/handler.rs @@ -87,8 +87,7 @@ pub trait SendEvent: Handler { /// /// This method is responsible for routing events toward descendents. /// [`WidgetId`] values are assigned via depth-first search with parents - /// ordered after all children. Disabling a widget is recursive, hence - /// disabled widgets should not forward any events. + /// ordered after all children. /// /// The following logic is recommended for routing events: /// ```no_test @@ -112,6 +111,12 @@ pub trait SendEvent: Handler { /// The example above uses [`EventMgr::handle_generic`], which is an optional /// tool able to perform some simplifications on events. It is also valid to /// call [`Handler::handle`] directly or simply to embed handling logic here. + /// + /// When a child widget returns [`Response::Unused`], the widget may call + /// its own event handler. This is useful e.g. to capture a click+drag on a + /// child which does not handle that event. Note further that in case the + /// child is disabled, events targetting the child may be sent directly to + /// self. fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response; } diff --git a/crates/kas-widgets/src/combobox.rs b/crates/kas-widgets/src/combobox.rs index 2ad1ea80f..1649d61da 100644 --- a/crates/kas-widgets/src/combobox.rs +++ b/crates/kas-widgets/src/combobox.rs @@ -77,6 +77,22 @@ widget! { } Response::Used } + Event::Command(cmd, _) => { + let next = |mgr: &mut EventMgr, s, clr, rev| { + if clr { + mgr.clear_nav_focus(); + } + mgr.next_nav_focus(s, rev, true); + Response::Used + }; + match cmd { + Command::Up => next(mgr, self, false, true), + Command::Down => next(mgr, self, false, false), + Command::Home => next(mgr, self, true, false), + Command::End => next(mgr, self, true, true), + _ => Response::Unused, + } + } Event::PressStart { source, start_id, @@ -338,25 +354,7 @@ impl ComboBox { r: Response<(usize, ())>, ) -> Response { match r { - Response::Unused => match event { - Event::Command(cmd, _) => { - let next = |mgr: &mut EventMgr, s, clr, rev| { - if clr { - mgr.clear_nav_focus(); - } - mgr.next_nav_focus(s, rev, true); - Response::Used - }; - match cmd { - Command::Up => next(mgr, self, false, true), - Command::Down => next(mgr, self, false, false), - Command::Home => next(mgr, self, true, false), - Command::End => next(mgr, self, true, true), - _ => Response::Unused, - } - } - _ => Response::Unused, - }, + Response::Unused => EventMgr::handle_generic(self, mgr, event), Response::Update | Response::Select => { if let Some(id) = self.popup_id { mgr.close_window(id, true); diff --git a/crates/kas-widgets/src/menu/submenu.rs b/crates/kas-widgets/src/menu/submenu.rs index 166ffdd8e..76e6dd152 100644 --- a/crates/kas-widgets/src/menu/submenu.rs +++ b/crates/kas-widgets/src/menu/submenu.rs @@ -181,29 +181,23 @@ widget! { return Response::Unused; } - if self.eq_id(&id) { - EventMgr::handle_generic(self, mgr, event) - } else { + if !self.eq_id(&id) { let r = self.list.send(mgr, id.clone(), event.clone()); match r { - Response::Unused => match event { - Event::Command(cmd, _) if self.popup_id.is_some() => { - self.handle_dir_key(mgr, cmd) - } - _ => Response::Unused, - }, + Response::Unused => (), Response::Select => { self.set_menu_path(mgr, Some(&id), true); - Response::Used + return Response::Used; } r @ (Response::Update | Response::Msg(_)) => { self.close_menu(mgr, true); - r + return r; } - r => r, + r => return r, } } + EventMgr::handle_generic(self, mgr, event) } } diff --git a/crates/kas-widgets/src/view/list_view.rs b/crates/kas-widgets/src/view/list_view.rs index c76df4671..037bcd3f5 100644 --- a/crates/kas-widgets/src/view/list_view.rs +++ b/crates/kas-widgets/src/view/list_view.rs @@ -622,6 +622,15 @@ widget! { } return Response::Used; } + Event::PressStart { source, coord, .. } => { + return if source.is_primary() { + mgr.grab_press_unique(self.id(), source, coord, None); + self.press_phase = PressPhase::Pan; + Response::Used + } else { + Response::Unused + }; + } Event::PressMove { coord, .. } => { if let PressPhase::Start(start_coord) = self.press_phase { if mgr.config_test_pan_thresh(coord - start_coord) { diff --git a/crates/kas-widgets/src/view/matrix_view.rs b/crates/kas-widgets/src/view/matrix_view.rs index 41c431e63..749294199 100644 --- a/crates/kas-widgets/src/view/matrix_view.rs +++ b/crates/kas-widgets/src/view/matrix_view.rs @@ -622,6 +622,15 @@ widget! { } return Response::Used; } + Event::PressStart { source, coord, .. } => { + return if source.is_primary() { + mgr.grab_press_unique(self.id(), source, coord, None); + self.press_phase = PressPhase::Pan; + Response::Used + } else { + Response::Unused + }; + } Event::PressMove { coord, .. } => { if let PressPhase::Start(start_coord) = self.press_phase { if mgr.config_test_pan_thresh(coord - start_coord) { From a59f4318690d9128cc58621378f61dddf14235f9 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sat, 5 Mar 2022 17:28:00 +0000 Subject: [PATCH 08/16] Replace per-widget disabled state with list in event state --- crates/kas-core/src/core/data.rs | 2 - crates/kas-core/src/core/widget.rs | 43 ++------------- crates/kas-core/src/event/handler.rs | 3 -- crates/kas-core/src/event/manager.rs | 34 +++++++++--- crates/kas-core/src/event/manager/mgr_pub.rs | 53 +++++++++++++++---- .../kas-core/src/event/manager/mgr_shell.rs | 1 + crates/kas-core/src/theme/draw.rs | 20 +++---- crates/kas-macros/src/widget.rs | 4 -- crates/kas-resvg/src/canvas.rs | 2 +- crates/kas-resvg/src/svg.rs | 2 +- crates/kas-wgpu/src/window.rs | 2 +- crates/kas-widgets/src/adapter/map.rs | 4 -- crates/kas-widgets/src/button.rs | 3 -- crates/kas-widgets/src/checkbox.rs | 2 +- crates/kas-widgets/src/combobox.rs | 6 +-- crates/kas-widgets/src/edit_field.rs | 6 +-- crates/kas-widgets/src/grid.rs | 32 ++++++----- crates/kas-widgets/src/label.rs | 10 ++-- crates/kas-widgets/src/list.rs | 32 ++++++----- crates/kas-widgets/src/menu/menu_entry.rs | 4 +- crates/kas-widgets/src/menu/menubar.rs | 4 -- crates/kas-widgets/src/menu/submenu.rs | 6 +-- crates/kas-widgets/src/progress.rs | 2 +- crates/kas-widgets/src/radiobox.rs | 2 +- crates/kas-widgets/src/scroll.rs | 6 +-- crates/kas-widgets/src/scroll_label.rs | 2 +- crates/kas-widgets/src/scrollbar.rs | 14 ++--- crates/kas-widgets/src/separator.rs | 2 +- crates/kas-widgets/src/slider.rs | 6 +-- crates/kas-widgets/src/splitter.rs | 4 +- crates/kas-widgets/src/sprite.rs | 2 +- crates/kas-widgets/src/stack.rs | 22 ++++---- crates/kas-widgets/src/view/list_view.rs | 6 +-- crates/kas-widgets/src/view/matrix_view.rs | 6 +-- crates/kas-widgets/src/view/single_view.rs | 4 -- crates/kas-widgets/src/window.rs | 4 +- examples/gallery.rs | 2 +- 37 files changed, 154 insertions(+), 205 deletions(-) diff --git a/crates/kas-core/src/core/data.rs b/crates/kas-core/src/core/data.rs index a00a63df8..994c61a63 100644 --- a/crates/kas-core/src/core/data.rs +++ b/crates/kas-core/src/core/data.rs @@ -44,7 +44,6 @@ pub struct CoreData { pub layout: StorageChain, pub rect: Rect, pub id: WidgetId, - pub disabled: bool, } /// Note: the clone has default-initialised layout storage and identifier. @@ -55,7 +54,6 @@ impl Clone for CoreData { layout: StorageChain::default(), rect: self.rect, id: WidgetId::default(), - disabled: self.disabled, } } } diff --git a/crates/kas-core/src/core/widget.rs b/crates/kas-core/src/core/widget.rs index 22a25d064..8c076bdd0 100644 --- a/crates/kas-core/src/core/widget.rs +++ b/crates/kas-core/src/core/widget.rs @@ -17,7 +17,7 @@ use crate::layout::{self, AlignHints, AxisInfo, SetRectMgr, SizeRules}; use crate::theme::DrawCtx; use crate::theme::{DrawMgr, SizeMgr}; use crate::util::IdentifyWidget; -use crate::{CoreData, TkAction, WidgetId}; +use crate::{CoreData, WidgetId}; impl dyn WidgetCore { /// Forwards to the method defined on the type `Any`. @@ -65,24 +65,6 @@ pub trait WidgetCore: Any + fmt::Debug { #[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] fn core_data_mut(&mut self) -> &mut CoreData; - /// Set disabled state (chaining) - /// - /// This is identical to [`WidgetExt::set_disabled`], but can be called in - /// chaining fashion. Example: - /// ```ignore - /// use kas::{WidgetCore, widget::MenuEntry}; - /// let entry = MenuEntry::new("Disabled Item", ()).with_disabled(true); - /// ``` - #[inline] - #[must_use] - fn with_disabled(mut self, disabled: bool) -> Self - where - Self: Sized, - { - self.core_data_mut().disabled = disabled; - self - } - /// Get the name of the widget struct fn widget_name(&self) -> &'static str; @@ -401,12 +383,12 @@ pub trait Layout: WidgetChildren { /// This method is invoked each frame to draw visible widgets. It should /// draw itself and recurse into all visible children. /// - /// One should use `let draw = draw.with_core(self.core_data());` to obtain + /// One should use `let draw = draw.with_id(self.id_ref());` to obtain /// a [`DrawCtx`], enabling further drawing. /// /// The default impl draws elements as defined by [`Self::layout`]. fn draw(&mut self, mut draw: DrawMgr) { - let draw = draw.with_core(self.core_data()); + let draw = draw.with_id(self.id_ref()); self.layout().draw(draw); } } @@ -531,25 +513,6 @@ pub trait WidgetExt: WidgetChildren { } } - /// Get whether the widget is disabled - #[inline] - fn is_disabled(&self) -> bool { - self.core_data().disabled - } - - /// Set the disabled state of a widget - /// - /// If disabled, a widget should not respond to input and should appear - /// greyed out. - /// - /// The disabled status is inherited by children: events should not be - /// passed to them, and they should also be drawn greyed out. - #[inline] - fn set_disabled(&mut self, disabled: bool) -> TkAction { - self.core_data_mut().disabled = disabled; - TkAction::REDRAW - } - /// Get the widget's region, relative to its parent. #[inline] fn rect(&self) -> Rect { diff --git a/crates/kas-core/src/event/handler.rs b/crates/kas-core/src/event/handler.rs index 4e996f9f7..d86f56cc6 100644 --- a/crates/kas-core/src/event/handler.rs +++ b/crates/kas-core/src/event/handler.rs @@ -91,9 +91,6 @@ pub trait SendEvent: Handler { /// /// The following logic is recommended for routing events: /// ```no_test - /// if self.is_disabled() { - /// return Response::Unused; - /// } /// match self.find_child_index(&id) { /// Some(widget_index![self.child0]) => self.child0.send(mgr, id, event).into(), /// Some(widget_index![self.child1]) => self.child1.send(mgr, id, event).into(), diff --git a/crates/kas-core/src/event/manager.rs b/crates/kas-core/src/event/manager.rs index 7fb54288c..aa7cd8940 100644 --- a/crates/kas-core/src/event/manager.rs +++ b/crates/kas-core/src/event/manager.rs @@ -146,6 +146,7 @@ type AccelLayer = (bool, HashMap); #[derive(Debug)] pub struct EventState { config: WindowConfig, + disabled: Vec, scale_factor: f32, configure_active: bool, configure_count: usize, @@ -401,11 +402,13 @@ impl<'a> EventMgr<'a> { if let Some(id) = w_id { let mut icon = Default::default(); - if let Some(w) = widget.find_widget(&id) { - if w.hover_highlight() { - self.redraw(id); + if !self.is_disabled(&id) { + if let Some(w) = widget.find_widget(&id) { + if w.hover_highlight() { + self.redraw(id); + } + icon = w.cursor_icon(); } - icon = w.cursor_icon(); } if icon != self.state.hover_icon { self.state.hover_icon = icon; @@ -543,9 +546,26 @@ impl<'a> EventMgr<'a> { } } + fn send_impl(&mut self, widget: &mut W, mut id: WidgetId, event: Event) -> Response + where + W: Widget + ?Sized, + { + // TODO(opt): we should be able to use binary search here + for d in &self.disabled { + if d.is_ancestor_of(&id) { + if let Some(p) = d.parent() { + id = p; + } else { + return Response::Unused; + } + } + } + widget.send(self, id, event) + } + fn send_event(&mut self, widget: &mut W, id: WidgetId, event: Event) { trace!("Send to {}: {:?}", id, event); - let _ = widget.send(self, id, event); + let _ = self.send_impl(widget, id, event); } // Similar to send_event, but return true only if response != Response::Unused @@ -556,7 +576,7 @@ impl<'a> EventMgr<'a> { event: Event, ) -> bool { trace!("Send to {}: {:?}", id, event); - let r = widget.send(self, id, event); + let r = self.send_impl(widget, id, event); !matches!(r, Response::Unused) } @@ -571,7 +591,7 @@ impl<'a> EventMgr<'a> { .map(|(wid, p, _)| (*wid, p.parent.clone())) { trace!("Send to popup parent: {}: {:?}", parent, event); - match widget.send(self, parent, event.clone()) { + match self.send_impl(widget, parent, event.clone()) { Response::Unused => (), _ => return, } diff --git a/crates/kas-core/src/event/manager/mgr_pub.rs b/crates/kas-core/src/event/manager/mgr_pub.rs index a5e2acda2..a38cb5aa2 100644 --- a/crates/kas-core/src/event/manager/mgr_pub.rs +++ b/crates/kas-core/src/event/manager/mgr_pub.rs @@ -17,7 +17,7 @@ use crate::layout::SetRectMgr; use crate::theme::{SizeMgr, ThemeControl}; #[allow(unused)] use crate::WidgetConfig; // for doc-links -use crate::{CoreData, TkAction, WidgetExt, WidgetId, WindowId}; +use crate::{TkAction, WidgetExt, WidgetId, WindowId}; impl<'a> std::ops::BitOrAssign for EventMgr<'a> { #[inline] @@ -63,7 +63,6 @@ impl EventState { } /// Check whether the given widget is visually depressed - #[inline] pub fn is_depressed(&self, w_id: &WidgetId) -> bool { for (_, id) in &self.key_depress { if *id == w_id { @@ -91,10 +90,23 @@ impl EventState { false } + /// Check whether a widget is disabled + /// + /// A widget is disabled if any ancestor is. + #[inline] + pub fn is_disabled(&self, w_id: &WidgetId) -> bool { + // TODO(opt): we should be able to use binary search here + for id in &self.disabled { + if id.is_ancestor_of(w_id) { + return true; + } + } + false + } + /// Construct [`InputState`] /// - /// The `disabled` flag is inherited from parents. [`InputState::disabled`] - /// will be true if either `disabled` or `self.is_disabled()` are true. + /// [`InputState::DISABLED`] will be set if any parent is disabled. /// /// The error state defaults to `false` since most widgets don't support /// this. @@ -103,19 +115,19 @@ impl EventState { /// in `hover` status will not (since this happens frequently and many /// widgets are unaffected), unless [`WidgetConfig::hover_highlight`] /// returns true. - pub fn draw_state(&self, core: &CoreData, disabled: bool) -> InputState { - let (char_focus, sel_focus) = self.has_char_focus(&core.id); + pub fn draw_state(&self, id: &WidgetId) -> InputState { + let (char_focus, sel_focus) = self.has_char_focus(id); let mut state = InputState::empty(); - if core.disabled || disabled { + if self.is_disabled(id) { state |= InputState::DISABLED; } - if self.is_hovered(&core.id) { + if self.is_hovered(id) { state |= InputState::HOVER; } - if self.is_depressed(&core.id) { + if self.is_depressed(id) { state |= InputState::DEPRESS; } - if self.has_nav_focus(&core.id) { + if self.has_nav_focus(id) { state |= InputState::NAV_FOCUS; } if char_focus { @@ -169,6 +181,25 @@ impl EventState { self.scale_factor } + /// Set/unset a widget as disabled + /// + /// Disabled status applies to all descendants. + pub fn set_disabled(&mut self, w_id: WidgetId, state: bool) { + for (i, id) in self.disabled.iter().enumerate() { + if w_id == id { + if !state { + self.redraw(w_id); + self.disabled.remove(i); + } + return; + } + } + if state { + self.redraw(w_id.clone()); + self.disabled.push(w_id); + } + } + /// Schedule an update /// /// Widgets requiring animation should schedule an update; as a result, @@ -792,7 +823,7 @@ impl<'a> EventMgr<'a> { focus: Option<&WidgetId>, rev: bool, ) -> Option { - if widget.is_disabled() { + if mgr.ev_state().is_disabled(widget.id_ref()) { return None; } diff --git a/crates/kas-core/src/event/manager/mgr_shell.rs b/crates/kas-core/src/event/manager/mgr_shell.rs index 1e651c090..85b2d4298 100644 --- a/crates/kas-core/src/event/manager/mgr_shell.rs +++ b/crates/kas-core/src/event/manager/mgr_shell.rs @@ -32,6 +32,7 @@ impl EventState { pub fn new(config: Rc>, scale_factor: f32) -> Self { EventState { config: WindowConfig::new(config, scale_factor), + disabled: vec![], scale_factor, configure_active: false, configure_count: 0, diff --git a/crates/kas-core/src/theme/draw.rs b/crates/kas-core/src/theme/draw.rs index 579658e80..954b70ef5 100644 --- a/crates/kas-core/src/theme/draw.rs +++ b/crates/kas-core/src/theme/draw.rs @@ -15,7 +15,7 @@ use crate::event::EventState; use crate::geom::{Coord, Offset, Rect}; use crate::layout::SetRectMgr; use crate::text::{AccelString, Text, TextApi, TextDisplay}; -use crate::{CoreData, TkAction}; +use crate::{TkAction, WidgetId}; /// Draw interface /// @@ -25,15 +25,14 @@ use crate::{CoreData, TkAction}; /// Use [`DrawMgr::with_core`] to access draw methods. pub struct DrawMgr<'a> { h: &'a mut dyn DrawHandle, - disabled: bool, } impl<'a> DrawMgr<'a> { /// Construct from a [`DrawMgr`] and [`EventState`] #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] #[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] - pub fn new(h: &'a mut dyn DrawHandle, disabled: bool) -> Self { - DrawMgr { h, disabled } + pub fn new(h: &'a mut dyn DrawHandle) -> Self { + DrawMgr { h } } /// Access event-management state @@ -68,10 +67,10 @@ impl<'a> DrawMgr<'a> { } /// Add context to allow draw operations - pub fn with_core<'b>(&'b mut self, core: &CoreData) -> DrawCtx<'b> { - let state = self.h.components().2.draw_state(core, self.disabled); + pub fn with_id<'b>(&'b mut self, id: &WidgetId) -> DrawCtx<'b> { + let state = self.h.components().2.draw_state(id); let h = &mut *self.h; - let wid = core.id.as_u64(); + let wid = id.as_u64(); DrawCtx { h, wid, state } } } @@ -103,10 +102,7 @@ impl<'a> DrawCtx<'a> { where 'a: 'b, { - DrawMgr { - h: self.h, - disabled: self.state.contains(InputState::DISABLED), - } + DrawMgr { h: self.h } } /// Reborrow as a [`DrawCtx`] with a new lifetime @@ -608,7 +604,7 @@ mod test { // But we don't need to: we just want to test that methods are callable. let _scale = draw.size_mgr().scale_factor(); - let mut draw = draw.with_core(&Default::default()); + let mut draw = draw.with_id(&WidgetId::ROOT); let text = crate::text::Text::new_single("sample"); let class = TextClass::Label; diff --git a/crates/kas-macros/src/widget.rs b/crates/kas-macros/src/widget.rs index 271a9f699..294441681 100644 --- a/crates/kas-macros/src/widget.rs +++ b/crates/kas-macros/src/widget.rs @@ -417,10 +417,6 @@ pub(crate) fn widget(mut args: Widget) -> Result { quote! { use ::kas::{event::Response, WidgetCore, WidgetChildren, WidgetExt}; - if self.is_disabled() { - return Response::Unused; - } - match self.find_child_index(&id) { #ev_to_num _ if id == self.core_data().id => ::kas::event::EventMgr::handle_generic(self, mgr, event), diff --git a/crates/kas-resvg/src/canvas.rs b/crates/kas-resvg/src/canvas.rs index 2401b7de8..0b086e903 100644 --- a/crates/kas-resvg/src/canvas.rs +++ b/crates/kas-resvg/src/canvas.rs @@ -138,7 +138,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_core(self.core_data()); + let mut draw = draw.with_id(self.id_ref()); let (redraw, animate) = self.program.do_redraw_animate(); if redraw { draw.set_rect_mgr(|mgr| self.redraw(mgr)); diff --git a/crates/kas-resvg/src/svg.rs b/crates/kas-resvg/src/svg.rs index 685b39fc9..53d59ef37 100644 --- a/crates/kas-resvg/src/svg.rs +++ b/crates/kas-resvg/src/svg.rs @@ -185,7 +185,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_core(self.core_data()); + let mut draw = draw.with_id(self.id_ref()); if let Some(id) = self.image_id { draw.image(id, self.rect()); } diff --git a/crates/kas-wgpu/src/window.rs b/crates/kas-wgpu/src/window.rs index 15b5d73c1..fb6a1a61b 100644 --- a/crates/kas-wgpu/src/window.rs +++ b/crates/kas-wgpu/src/window.rs @@ -356,7 +356,7 @@ impl>> Window { shared .theme .draw_handle(draw, &mut self.ev_state, &mut self.theme_window); - let draw_mgr = DrawMgr::new(&mut draw_handle, false); + let draw_mgr = DrawMgr::new(&mut draw_handle); self.widget.draw(draw_mgr); } #[cfg(feature = "gat")] diff --git a/crates/kas-widgets/src/adapter/map.rs b/crates/kas-widgets/src/adapter/map.rs index ab3d01cda..134d108e9 100644 --- a/crates/kas-widgets/src/adapter/map.rs +++ b/crates/kas-widgets/src/adapter/map.rs @@ -50,10 +50,6 @@ widget! { impl SendEvent for Self { fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - if self.is_disabled() { - return Response::Unused; - } - if self.eq_id(&id) { self.handle(mgr, event) } else { diff --git a/crates/kas-widgets/src/button.rs b/crates/kas-widgets/src/button.rs index 2b108d4a8..122023297 100644 --- a/crates/kas-widgets/src/button.rs +++ b/crates/kas-widgets/src/button.rs @@ -153,9 +153,6 @@ widget! { impl SendEvent for Self { fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - if self.is_disabled() { - return Response::Unused; - } if self.eq_id(&id) { EventMgr::handle_generic(self, mgr, event) } else { diff --git a/crates/kas-widgets/src/checkbox.rs b/crates/kas-widgets/src/checkbox.rs index abb52db64..8dd3db398 100644 --- a/crates/kas-widgets/src/checkbox.rs +++ b/crates/kas-widgets/src/checkbox.rs @@ -41,7 +41,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - draw.with_core(self.core_data()).checkbox(self.core.rect, self.state); + draw.with_id(self.id_ref()).checkbox(self.core.rect, self.state); } } diff --git a/crates/kas-widgets/src/combobox.rs b/crates/kas-widgets/src/combobox.rs index 1649d61da..09eaa7986 100644 --- a/crates/kas-widgets/src/combobox.rs +++ b/crates/kas-widgets/src/combobox.rs @@ -49,7 +49,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let draw = draw.with_core(self.core_data()); + let draw = draw.with_id(self.id_ref()); self.layout().draw(draw); } } @@ -161,10 +161,6 @@ widget! { impl event::SendEvent for Self { fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - if self.is_disabled() { - return Response::Unused; - } - if self.eq_id(&id) { EventMgr::handle_generic(self, mgr, event) } else { diff --git a/crates/kas-widgets/src/edit_field.rs b/crates/kas-widgets/src/edit_field.rs index bad278cf4..95ae390ef 100644 --- a/crates/kas-widgets/src/edit_field.rs +++ b/crates/kas-widgets/src/edit_field.rs @@ -188,11 +188,11 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_core(self.core_data()); + let mut draw = draw.with_id(self.id_ref()); let error = self.inner.has_error(); { let mut draw = draw.re(); - let mut draw = draw.with_core(self.inner.core_data()); + let mut draw = draw.with_id(self.inner.id_ref()); if error { draw.state.insert(InputState::ERROR); } @@ -421,7 +421,7 @@ widget! { } else { TextClass::Edit }; - let mut draw = draw.with_core(self.core_data()); + let mut draw = draw.with_id(self.id_ref()); draw.with_clip_region(self.rect(), self.view_offset, |mut draw| { if self.selection.is_empty() { draw.text(self.rect().pos, self.text.as_ref(), class); diff --git a/crates/kas-widgets/src/grid.rs b/crates/kas-widgets/src/grid.rs index 31acd9620..ecc05018f 100644 --- a/crates/kas-widgets/src/grid.rs +++ b/crates/kas-widgets/src/grid.rs @@ -82,23 +82,21 @@ widget! { impl event::SendEvent for Self { fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - if !self.is_disabled() { - if let Some(index) = self.find_child_index(&id) { - if let Some((_, child)) = self.widgets.get_mut(index) { - let r = child.send(mgr, id.clone(), event); - return match Response::try_from(r) { - Ok(r) => r, - Err(msg) => { - log::trace!( - "Received by {} from {}: {:?}", - self.id(), - id, - kas::util::TryFormat(&msg) - ); - Response::Msg(msg) - } - }; - } + if let Some(index) = self.find_child_index(&id) { + if let Some((_, child)) = self.widgets.get_mut(index) { + let r = child.send(mgr, id.clone(), event); + return match Response::try_from(r) { + Ok(r) => r, + Err(msg) => { + log::trace!( + "Received by {} from {}: {:?}", + self.id(), + id, + kas::util::TryFormat(&msg) + ); + Response::Msg(msg) + } + }; } } diff --git a/crates/kas-widgets/src/label.rs b/crates/kas-widgets/src/label.rs index bac489037..93e2fc14f 100644 --- a/crates/kas-widgets/src/label.rs +++ b/crates/kas-widgets/src/label.rs @@ -37,12 +37,12 @@ widget! { #[cfg(feature = "min_spec")] default fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_core(self.core_data()); + let mut draw = draw.with_id(self.id_ref()); draw.text_effects(self.core.rect.pos, &self.label, TextClass::Label); } #[cfg(not(feature = "min_spec"))] fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_core(self.core_data()); + let mut draw = draw.with_id(self.id_ref()); draw.text_effects(self.core.rect.pos, &self.label, TextClass::Label); } } @@ -66,7 +66,7 @@ widget! { #[cfg(feature = "min_spec")] impl Layout for AccelLabel { fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_core(self.core_data()); + let mut draw = draw.with_id(self.id_ref()); let accel = draw.ev_state().show_accel_labels(); draw.text_accel(self.core.rect.pos, &self.label, accel, TextClass::Label); } @@ -76,14 +76,14 @@ impl Layout for AccelLabel { #[cfg(feature = "min_spec")] impl<'a> Layout for Label<&'a str> { fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_core(self.core_data()); + let mut draw = draw.with_id(self.id_ref()); draw.text(self.core.rect.pos, self.label.as_ref(), TextClass::Label); } } #[cfg(feature = "min_spec")] impl Layout for StringLabel { fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_core(self.core_data()); + let mut draw = draw.with_id(self.id_ref()); draw.text(self.core.rect.pos, self.label.as_ref(), TextClass::Label); } } diff --git a/crates/kas-widgets/src/list.rs b/crates/kas-widgets/src/list.rs index 6152850e2..9623bae4d 100644 --- a/crates/kas-widgets/src/list.rs +++ b/crates/kas-widgets/src/list.rs @@ -203,23 +203,21 @@ widget! { impl event::SendEvent for Self { fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - if !self.is_disabled() { - if let Some(index) = self.find_child_index(&id) { - if let Some(child) = self.widgets.get_mut(index) { - let r = child.send(mgr, id.clone(), event); - return match Response::try_from(r) { - Ok(r) => r, - Err(msg) => { - log::trace!( - "Received by {} from {}: {:?}", - self.id(), - id, - kas::util::TryFormat(&msg) - ); - Response::Msg(FromIndexed::from_indexed(index, msg)) - } - }; - } + if let Some(index) = self.find_child_index(&id) { + if let Some(child) = self.widgets.get_mut(index) { + let r = child.send(mgr, id.clone(), event); + return match Response::try_from(r) { + Ok(r) => r, + Err(msg) => { + log::trace!( + "Received by {} from {}: {:?}", + self.id(), + id, + kas::util::TryFormat(&msg) + ); + Response::Msg(FromIndexed::from_indexed(index, msg)) + } + }; } } diff --git a/crates/kas-widgets/src/menu/menu_entry.rs b/crates/kas-widgets/src/menu/menu_entry.rs index cc6ea631d..2f4ea7f65 100644 --- a/crates/kas-widgets/src/menu/menu_entry.rs +++ b/crates/kas-widgets/src/menu/menu_entry.rs @@ -40,7 +40,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_core(self.core_data()); + let mut draw = draw.with_id(self.id_ref()); draw.frame(self.core.rect, FrameStyle::MenuEntry); draw.text_accel( self.layout_label.pos, @@ -143,7 +143,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_core(self.checkbox.core_data()); + let mut draw = draw.with_id(self.checkbox.id_ref()); draw.frame(self.core.rect, FrameStyle::MenuEntry); self.layout().draw(draw); } diff --git a/crates/kas-widgets/src/menu/menubar.rs b/crates/kas-widgets/src/menu/menubar.rs index d528e41f1..d85561524 100644 --- a/crates/kas-widgets/src/menu/menubar.rs +++ b/crates/kas-widgets/src/menu/menubar.rs @@ -179,10 +179,6 @@ widget! { impl event::SendEvent for Self { fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - if self.is_disabled() { - return Response::Unused; - } - if self.eq_id(&id) { self.handle(mgr, event) } else { diff --git a/crates/kas-widgets/src/menu/submenu.rs b/crates/kas-widgets/src/menu/submenu.rs index 76e6dd152..c80fc146d 100644 --- a/crates/kas-widgets/src/menu/submenu.rs +++ b/crates/kas-widgets/src/menu/submenu.rs @@ -143,7 +143,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_core(self.core_data()); + let mut draw = draw.with_id(self.id_ref()); draw.frame(self.core.rect, FrameStyle::MenuEntry); draw.text_accel( self.label_store.pos, @@ -177,10 +177,6 @@ widget! { impl event::SendEvent for Self { fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - if self.is_disabled() { - return Response::Unused; - } - if !self.eq_id(&id) { let r = self.list.send(mgr, id.clone(), event.clone()); diff --git a/crates/kas-widgets/src/progress.rs b/crates/kas-widgets/src/progress.rs index eef40363f..9bf81fe5b 100644 --- a/crates/kas-widgets/src/progress.rs +++ b/crates/kas-widgets/src/progress.rs @@ -100,7 +100,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_core(self.core_data()); + let mut draw = draw.with_id(self.id_ref()); let dir = self.direction.as_direction(); draw.progress_bar(self.core.rect, dir, self.value); } diff --git a/crates/kas-widgets/src/radiobox.rs b/crates/kas-widgets/src/radiobox.rs index 6a1a40d8f..acfd018fb 100644 --- a/crates/kas-widgets/src/radiobox.rs +++ b/crates/kas-widgets/src/radiobox.rs @@ -95,7 +95,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_core(self.core_data()); + let mut draw = draw.with_id(self.id_ref()); draw.radiobox(self.core.rect, self.state); } } diff --git a/crates/kas-widgets/src/scroll.rs b/crates/kas-widgets/src/scroll.rs index ea1c03cb1..77582fe6f 100644 --- a/crates/kas-widgets/src/scroll.rs +++ b/crates/kas-widgets/src/scroll.rs @@ -131,7 +131,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_core(self.core_data()); + let mut draw = draw.with_id(self.id_ref()); draw.with_clip_region(self.core.rect, self.scroll_offset(), |mut draw| { self.inner.draw(draw.re()) }); @@ -140,10 +140,6 @@ widget! { impl event::SendEvent for Self { fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - if self.is_disabled() { - return Response::Unused; - } - if self.inner.id().is_ancestor_of(&id) { let child_event = self.scroll.offset_event(event.clone()); match self.inner.send(mgr, id, child_event) { diff --git a/crates/kas-widgets/src/scroll_label.rs b/crates/kas-widgets/src/scroll_label.rs index 3260d4bb3..d7e8d521f 100644 --- a/crates/kas-widgets/src/scroll_label.rs +++ b/crates/kas-widgets/src/scroll_label.rs @@ -54,7 +54,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_core(self.core_data()); + let mut draw = draw.with_id(self.id_ref()); let class = TextClass::LabelScroll; draw.with_clip_region(self.rect(), self.view_offset, |mut draw| { if self.selection.is_empty() { diff --git a/crates/kas-widgets/src/scrollbar.rs b/crates/kas-widgets/src/scrollbar.rs index ba7dd146f..0a009112f 100644 --- a/crates/kas-widgets/src/scrollbar.rs +++ b/crates/kas-widgets/src/scrollbar.rs @@ -236,7 +236,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_core(self.handle.core_data()); + let mut draw = draw.with_id(self.handle.id_ref()); let dir = self.direction.as_direction(); draw.scrollbar(self.core.rect, self.handle.rect(), dir); } @@ -244,10 +244,6 @@ widget! { impl event::SendEvent for Self { fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - if self.is_disabled() { - return Response::Unused; - } - let offset = if self.eq_id(&id) { match event { Event::PressStart { source, coord, .. } => { @@ -516,7 +512,7 @@ widget! { } fn draw_(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_core(self.core_data()); + let mut draw = draw.with_id(self.id_ref()); if self.show_bars.0 { self.horiz_bar.draw(draw.re()); } @@ -623,7 +619,7 @@ widget! { #[cfg(feature = "min_spec")] impl Layout for ScrollBars> { fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_core(self.core_data()); + let mut draw = draw.with_id(self.id_ref()); // Enlarge clip region to *our* rect: draw.with_clip_region(self.core.rect, self.inner.scroll_offset(), |mut draw| { self.inner.inner_mut().draw(draw.re()) @@ -642,10 +638,6 @@ widget! { impl event::SendEvent for Self { fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - if self.is_disabled() { - return Response::Unused; - } - match self.find_child_index(&id) { Some(widget_index![self.horiz_bar]) => self.horiz_bar .send(mgr, id, event) diff --git a/crates/kas-widgets/src/separator.rs b/crates/kas-widgets/src/separator.rs index 35c31a29a..f8faf661b 100644 --- a/crates/kas-widgets/src/separator.rs +++ b/crates/kas-widgets/src/separator.rs @@ -54,7 +54,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_core(self.core_data()); + let mut draw = draw.with_id(self.id_ref()); draw.separator(self.core.rect); } } diff --git a/crates/kas-widgets/src/slider.rs b/crates/kas-widgets/src/slider.rs index a67cbc265..de96fc221 100644 --- a/crates/kas-widgets/src/slider.rs +++ b/crates/kas-widgets/src/slider.rs @@ -254,7 +254,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_core(self.core_data()); + let mut draw = draw.with_id(self.id_ref()); if draw.ev_state().is_hovered(self.handle.id_ref()) { draw.state.insert(InputState::HOVER); } @@ -265,10 +265,6 @@ widget! { impl event::SendEvent for Self { fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - if self.is_disabled() { - return Response::Unused; - } - let offset = if self.handle.id().is_ancestor_of(&id) { match event { Event::NavFocus(key_focus) => { diff --git a/crates/kas-widgets/src/splitter.rs b/crates/kas-widgets/src/splitter.rs index f7e98d2f7..9ef330e38 100644 --- a/crates/kas-widgets/src/splitter.rs +++ b/crates/kas-widgets/src/splitter.rs @@ -195,7 +195,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_core(self.core_data()); + let mut draw = draw.with_id(self.id_ref()); // as with find_id, there's not much harm in invoking the solver twice let solver = layout::RowPositionSolver::new(self.direction); @@ -212,7 +212,7 @@ widget! { impl event::SendEvent for Self { fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - if !self.is_disabled() && !self.widgets.is_empty() { + if !self.widgets.is_empty() { if let Some(index) = self.find_child_index(&id) { if (index & 1) == 0 { if let Some(w) = self.widgets.get_mut(index >> 1) { diff --git a/crates/kas-widgets/src/sprite.rs b/crates/kas-widgets/src/sprite.rs index bb90d16f9..f87fdee56 100644 --- a/crates/kas-widgets/src/sprite.rs +++ b/crates/kas-widgets/src/sprite.rs @@ -49,7 +49,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_core(self.core_data()); + let mut draw = draw.with_id(self.id_ref()); if let Some(id) = self.id { draw.image(id, self.rect()); } diff --git a/crates/kas-widgets/src/stack.rs b/crates/kas-widgets/src/stack.rs index b22dcc647..86e334e2b 100644 --- a/crates/kas-widgets/src/stack.rs +++ b/crates/kas-widgets/src/stack.rs @@ -79,7 +79,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_core(self.core_data()); + let mut draw = draw.with_id(self.id_ref()); if self.active < self.widgets.len() { self.widgets[self.active].draw(draw.re()); } @@ -88,17 +88,15 @@ widget! { impl event::SendEvent for Self { fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - if !self.is_disabled() { - if let Some(index) = self.find_child_index(&id) { - if let Some(child) = self.widgets.get_mut(index) { - return match child.send(mgr, id, event) { - Response::Focus(rect) => { - *mgr |= self.set_active(index); - Response::Focus(rect) - } - r => r, - }; - } + if let Some(index) = self.find_child_index(&id) { + if let Some(child) = self.widgets.get_mut(index) { + return match child.send(mgr, id, event) { + Response::Focus(rect) => { + *mgr |= self.set_active(index); + Response::Focus(rect) + } + r => r, + }; } } diff --git a/crates/kas-widgets/src/view/list_view.rs b/crates/kas-widgets/src/view/list_view.rs index 037bcd3f5..6d190eabc 100644 --- a/crates/kas-widgets/src/view/list_view.rs +++ b/crates/kas-widgets/src/view/list_view.rs @@ -592,7 +592,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_core(self.core_data()); + let mut draw = draw.with_id(self.id_ref()); let offset = self.scroll_offset(); draw.with_clip_region(self.core.rect, offset, |mut draw| { for child in &mut self.widgets[..self.cur_len.cast()] { @@ -742,10 +742,6 @@ widget! { impl SendEvent for Self { fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - if self.is_disabled() { - return Response::Unused; - } - if self.eq_id(&id) { return self.handle(mgr, event); } diff --git a/crates/kas-widgets/src/view/matrix_view.rs b/crates/kas-widgets/src/view/matrix_view.rs index 749294199..54245e6e5 100644 --- a/crates/kas-widgets/src/view/matrix_view.rs +++ b/crates/kas-widgets/src/view/matrix_view.rs @@ -586,7 +586,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_core(self.core_data()); + let mut draw = draw.with_id(self.id_ref()); let offset = self.scroll_offset(); let rect = self.rect() + offset; let num = self.cur_len.cast(); @@ -757,10 +757,6 @@ widget! { impl SendEvent for Self { fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - if self.is_disabled() { - return Response::Unused; - } - if self.eq_id(&id) { return self.handle(mgr, event); } diff --git a/crates/kas-widgets/src/view/single_view.rs b/crates/kas-widgets/src/view/single_view.rs index abaceb36c..bfafdb5e5 100644 --- a/crates/kas-widgets/src/view/single_view.rs +++ b/crates/kas-widgets/src/view/single_view.rs @@ -135,10 +135,6 @@ widget! { impl SendEvent for Self { fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - if self.is_disabled() { - return Response::Unused; - } - if self.eq_id(&id) { self.handle(mgr, event) } else { diff --git a/crates/kas-widgets/src/window.rs b/crates/kas-widgets/src/window.rs index 04db323ac..eb00f744f 100644 --- a/crates/kas-widgets/src/window.rs +++ b/crates/kas-widgets/src/window.rs @@ -50,7 +50,7 @@ widget! { #[inline] fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_core(self.core_data()); + let mut draw = draw.with_id(self.id_ref()); self.w.draw(draw.re()); for (_, popup) in &self.popups { if let Some(widget) = self.w.find_widget_mut(&popup.id) { @@ -64,7 +64,7 @@ widget! { impl SendEvent for Self where W::Msg: Into { fn send(&mut self, mgr: &mut EventMgr, id: WidgetId, event: Event) -> Response { - if self.is_disabled() || self.eq_id(&id) { + if self.eq_id(&id) { Response::Unused } else { self.w.send(mgr, id, event).into() diff --git a/examples/gallery.rs b/examples/gallery.rs index 270fba99e..f87e0aa8f 100644 --- a/examples/gallery.rs +++ b/examples/gallery.rs @@ -336,7 +336,7 @@ fn main() -> kas::shell::Result<()> { mgr.adjust_theme(|theme| theme.set_scheme(&name)); } Menu::Disabled(state) => { - *mgr |= self.gallery.set_disabled(state); + mgr.set_disabled(self.gallery.inner().id(), state); } Menu::Quit => { *mgr |= TkAction::EXIT; From 0fc373c74f6dfd6093060925704a79b07e7328b2 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 7 Mar 2022 07:01:31 +0000 Subject: [PATCH 09/16] Push &EventState into DrawHandle --- crates/kas-core/Cargo.toml | 2 +- crates/kas-core/src/core/widget.rs | 4 +- crates/kas-core/src/event/manager/mgr_pub.rs | 35 ----- crates/kas-core/src/event/mod.rs | 4 - crates/kas-core/src/prelude.rs | 2 +- crates/kas-core/src/theme/draw.rs | 139 +++++++++---------- crates/kas-core/src/theme/mod.rs | 74 ---------- crates/kas-resvg/src/canvas.rs | 2 +- crates/kas-resvg/src/svg.rs | 2 +- crates/kas-theme/Cargo.toml | 1 + crates/kas-theme/src/anim.rs | 13 +- crates/kas-theme/src/colors.rs | 104 +++++++++++++- crates/kas-theme/src/flat_theme.rs | 81 ++++++----- crates/kas-theme/src/lib.rs | 1 + crates/kas-theme/src/shaded_theme.rs | 68 ++++----- crates/kas-widgets/src/checkbox.rs | 2 +- crates/kas-widgets/src/combobox.rs | 2 +- crates/kas-widgets/src/edit_field.rs | 16 +-- crates/kas-widgets/src/label.rs | 10 +- crates/kas-widgets/src/menu/menu_entry.rs | 4 +- crates/kas-widgets/src/menu/submenu.rs | 2 +- crates/kas-widgets/src/progress.rs | 2 +- crates/kas-widgets/src/radiobox.rs | 2 +- crates/kas-widgets/src/scroll.rs | 2 +- crates/kas-widgets/src/scroll_label.rs | 2 +- crates/kas-widgets/src/scrollbar.rs | 6 +- crates/kas-widgets/src/separator.rs | 2 +- crates/kas-widgets/src/slider.rs | 6 +- crates/kas-widgets/src/splitter.rs | 2 +- crates/kas-widgets/src/sprite.rs | 2 +- crates/kas-widgets/src/stack.rs | 2 +- crates/kas-widgets/src/view/list_view.rs | 2 +- crates/kas-widgets/src/view/matrix_view.rs | 2 +- crates/kas-widgets/src/window.rs | 2 +- 34 files changed, 288 insertions(+), 314 deletions(-) diff --git a/crates/kas-core/Cargo.toml b/crates/kas-core/Cargo.toml index 91c61c496..3131d8caa 100644 --- a/crates/kas-core/Cargo.toml +++ b/crates/kas-core/Cargo.toml @@ -62,7 +62,7 @@ easy-cast = { git = "https://github.com/kas-gui/easy-cast.git", rev = "6bf6084bb log = "0.4" smallvec = "1.6.1" stack_dst = { version = "0.6", optional = true } -bitflags = "1.3.1" # only used without winit +bitflags = "1.3.1" unicode-segmentation = "1.7" linear-map = "1.2.0" thiserror = "1.0.23" diff --git a/crates/kas-core/src/core/widget.rs b/crates/kas-core/src/core/widget.rs index 8c076bdd0..f97d0cc3d 100644 --- a/crates/kas-core/src/core/widget.rs +++ b/crates/kas-core/src/core/widget.rs @@ -383,12 +383,12 @@ pub trait Layout: WidgetChildren { /// This method is invoked each frame to draw visible widgets. It should /// draw itself and recurse into all visible children. /// - /// One should use `let draw = draw.with_id(self.id_ref());` to obtain + /// One should use `let draw = draw.with_id(self.id());` to obtain /// a [`DrawCtx`], enabling further drawing. /// /// The default impl draws elements as defined by [`Self::layout`]. fn draw(&mut self, mut draw: DrawMgr) { - let draw = draw.with_id(self.id_ref()); + let draw = draw.with_id(self.id()); self.layout().draw(draw); } } diff --git a/crates/kas-core/src/event/manager/mgr_pub.rs b/crates/kas-core/src/event/manager/mgr_pub.rs index a38cb5aa2..7ff0da7f9 100644 --- a/crates/kas-core/src/event/manager/mgr_pub.rs +++ b/crates/kas-core/src/event/manager/mgr_pub.rs @@ -104,41 +104,6 @@ impl EventState { false } - /// Construct [`InputState`] - /// - /// [`InputState::DISABLED`] will be set if any parent is disabled. - /// - /// The error state defaults to `false` since most widgets don't support - /// this. - /// - /// Note: most state changes should automatically cause a redraw, but change - /// in `hover` status will not (since this happens frequently and many - /// widgets are unaffected), unless [`WidgetConfig::hover_highlight`] - /// returns true. - pub fn draw_state(&self, id: &WidgetId) -> InputState { - let (char_focus, sel_focus) = self.has_char_focus(id); - let mut state = InputState::empty(); - if self.is_disabled(id) { - state |= InputState::DISABLED; - } - if self.is_hovered(id) { - state |= InputState::HOVER; - } - if self.is_depressed(id) { - state |= InputState::DEPRESS; - } - if self.has_nav_focus(id) { - state |= InputState::NAV_FOCUS; - } - if char_focus { - state |= InputState::CHAR_FOCUS; - } - if sel_focus { - state |= InputState::SEL_FOCUS; - } - state - } - /// Get the current modifier state #[inline] pub fn modifiers(&self) -> ModifiersState { diff --git a/crates/kas-core/src/event/mod.rs b/crates/kas-core/src/event/mod.rs index 8fa9ad164..5c3c5a429 100644 --- a/crates/kas-core/src/event/mod.rs +++ b/crates/kas-core/src/event/mod.rs @@ -74,10 +74,6 @@ pub mod components; use smallvec::SmallVec; use std::fmt::Debug; -// doc imports -#[allow(unused)] -use crate::{theme::InputState, Layout, WidgetCore}; - #[cfg(feature = "winit")] pub use winit::event::{ModifiersState, MouseButton, VirtualKeyCode}; #[cfg(feature = "winit")] diff --git a/crates/kas-core/src/prelude.rs b/crates/kas-core/src/prelude.rs index 7eb76e0c7..61660e216 100644 --- a/crates/kas-core/src/prelude.rs +++ b/crates/kas-core/src/prelude.rs @@ -31,7 +31,7 @@ pub use crate::text::AccelString; #[doc(no_inline)] pub use crate::text::{EditableTextApi, Text, TextApi, TextApiExt}; #[doc(no_inline)] -pub use crate::theme::{DrawMgr, InputState, SizeMgr, ThemeControl}; +pub use crate::theme::{DrawMgr, SizeMgr, ThemeControl}; #[doc(no_inline)] pub use crate::CoreData; #[doc(no_inline)] diff --git a/crates/kas-core/src/theme/draw.rs b/crates/kas-core/src/theme/draw.rs index 954b70ef5..9bdd7514f 100644 --- a/crates/kas-core/src/theme/draw.rs +++ b/crates/kas-core/src/theme/draw.rs @@ -8,7 +8,7 @@ use std::convert::AsRef; use std::ops::{Bound, Deref, DerefMut, Range, RangeBounds}; -use super::{FrameStyle, InputState, SizeHandle, SizeMgr, TextClass}; +use super::{FrameStyle, SizeHandle, SizeMgr, TextClass}; use crate::dir::Direction; use crate::draw::{color::Rgb, Draw, DrawShared, ImageId, PassType}; use crate::event::EventState; @@ -67,11 +67,9 @@ impl<'a> DrawMgr<'a> { } /// Add context to allow draw operations - pub fn with_id<'b>(&'b mut self, id: &WidgetId) -> DrawCtx<'b> { - let state = self.h.components().2.draw_state(id); + pub fn with_id<'b>(&'b mut self, id: WidgetId) -> DrawCtx<'b> { let h = &mut *self.h; - let wid = id.as_u64(); - DrawCtx { h, wid, state } + DrawCtx { h, id } } } @@ -84,11 +82,9 @@ impl<'a> DrawMgr<'a> { /// obtained through a [`SizeMgr`], e.g. through [`Self::size_mgr`]. pub struct DrawCtx<'a> { h: &'a mut dyn DrawHandle, - wid: u64, - /// Input state for drawn objects - /// - /// Normally this is derived automatically, but it may be adjusted. - pub state: InputState, + // NOTE: it is a little unfortunate that we need a clone of the WidgetId + // here (a borrow won't work due to borrow conflicts). Still, it's cheap. + id: WidgetId, } impl<'a> DrawCtx<'a> { @@ -116,8 +112,7 @@ impl<'a> DrawCtx<'a> { { DrawCtx { h: self.h, - wid: self.wid, - state: self.state, + id: self.id.clone(), } } @@ -157,10 +152,11 @@ impl<'a> DrawCtx<'a> { /// Adds a new draw pass of type [`PassType::Clip`], with draw operations /// clipped to `rect` and translated by `offset. pub fn with_clip_region(&mut self, rect: Rect, offset: Offset, mut f: F) { - let wid = self.wid; - let state = self.state; + // NOTE: using FnOnce in DrawHandle::new_pass would let us clone id outside the closure + let id = &self.id; self.h.new_pass(rect, offset, PassType::Clip, &mut |h| { - f(DrawCtx { h, wid, state }) + let id = id.clone(); + f(DrawCtx { h, id }) }); } @@ -173,11 +169,11 @@ impl<'a> DrawCtx<'a> { /// a frame or shadow around this overlay, thus the /// [`DrawCtx::get_clip_rect`] may be larger than expected. pub fn with_overlay(&mut self, rect: Rect, mut f: F) { - let wid = self.wid; - let state = self.state; + let id = &self.id; self.h .new_pass(rect, Offset::ZERO, PassType::Overlay, &mut |h| { - f(DrawCtx { h, wid, state }) + let id = id.clone(); + f(DrawCtx { h, id }) }); } @@ -197,7 +193,7 @@ impl<'a> DrawCtx<'a> { /// /// Note: for buttons, usage of [`Self::button`] does the same but allowing custom colours. pub fn frame(&mut self, rect: Rect, style: FrameStyle) { - self.h.frame(rect, style, self.state) + self.h.frame(&self.id, rect, style) } /// Draw a separator in the given `rect` @@ -219,7 +215,7 @@ impl<'a> DrawCtx<'a> { /// [`SizeMgr::text_bound`] should be called prior to this method to /// select a font, font size and wrap options (based on the [`TextClass`]). pub fn text(&mut self, pos: Coord, text: &TextDisplay, class: TextClass) { - self.h.text(pos, text, class, self.state); + self.h.text(&self.id, pos, text, class); } /// Draw text with effects @@ -231,7 +227,7 @@ impl<'a> DrawCtx<'a> { /// [`SizeMgr::text_bound`] should be called prior to this method to /// select a font, font size and wrap options (based on the [`TextClass`]). pub fn text_effects(&mut self, pos: Coord, text: &dyn TextApi, class: TextClass) { - self.h.text_effects(pos, text, class, self.state); + self.h.text_effects(&self.id, pos, text, class); } /// Draw an `AccelString` text @@ -241,7 +237,7 @@ impl<'a> DrawCtx<'a> { /// [`SizeMgr::text_bound`] should be called prior to this method to /// select a font, font size and wrap options (based on the [`TextClass`]). pub fn text_accel(&mut self, pos: Coord, text: &Text, class: TextClass) { - self.h.text_accel(pos, text, class, self.state); + self.h.text_accel(&self.id, pos, text, class); } /// Draw some text using the standard font, with a subset selected @@ -268,7 +264,7 @@ impl<'a> DrawCtx<'a> { }; let range = Range { start, end }; self.h - .text_selected_range(pos, text.as_ref(), range, class, self.state); + .text_selected_range(&self.id, pos, text.as_ref(), range, class); } /// Draw an edit marker at the given `byte` index on this `text` @@ -276,7 +272,7 @@ impl<'a> DrawCtx<'a> { /// [`SizeMgr::text_bound`] should be called prior to this method to /// select a font, font size and wrap options (based on the [`TextClass`]). pub fn text_cursor(&mut self, pos: Coord, text: &TextDisplay, class: TextClass, byte: usize) { - self.h.text_cursor(self.wid, pos, text, class, byte); + self.h.text_cursor(&self.id, pos, text, class, byte); } /// Draw button sides, background and margin-area highlight @@ -284,7 +280,7 @@ impl<'a> DrawCtx<'a> { /// Optionally, a specific colour may be used. // TODO: Allow theme-provided named colours? pub fn button(&mut self, rect: Rect, col: Option) { - self.h.button(rect, col, self.state); + self.h.button(&self.id, rect, col); } /// Draw UI element: checkbox @@ -293,14 +289,14 @@ impl<'a> DrawCtx<'a> { /// mark. A checkbox widget may include a text label, but that label is not /// part of this element. pub fn checkbox(&mut self, rect: Rect, checked: bool) { - self.h.checkbox(self.wid, rect, checked, self.state); + self.h.checkbox(&self.id, rect, checked); } /// Draw UI element: radiobox /// /// This is similar in appearance to a checkbox. pub fn radiobox(&mut self, rect: Rect, checked: bool) { - self.h.radiobox(self.wid, rect, checked, self.state); + self.h.radiobox(&self.id, rect, checked); } /// Draw UI element: scrollbar @@ -310,7 +306,7 @@ impl<'a> DrawCtx<'a> { /// - `dir`: direction of bar /// - `state`: highlighting information pub fn scrollbar(&mut self, rect: Rect, h_rect: Rect, dir: Direction) { - self.h.scrollbar(rect, h_rect, dir, self.state); + self.h.scrollbar(&self.id, rect, h_rect, dir); } /// Draw UI element: slider @@ -320,7 +316,7 @@ impl<'a> DrawCtx<'a> { /// - `dir`: direction of slider (currently only LTR or TTB) /// - `state`: highlighting information pub fn slider(&mut self, rect: Rect, h_rect: Rect, dir: Direction) { - self.h.slider(rect, h_rect, dir, self.state); + self.h.slider(&self.id, rect, h_rect, dir); } /// Draw UI element: progress bar @@ -330,7 +326,7 @@ impl<'a> DrawCtx<'a> { /// - `state`: highlighting information /// - `value`: progress value, between 0.0 and 1.0 pub fn progress_bar(&mut self, rect: Rect, dir: Direction, value: f32) { - self.h.progress_bar(rect, dir, self.state, value); + self.h.progress_bar(&self.id, rect, dir, value); } /// Draw an image @@ -387,7 +383,7 @@ pub trait DrawHandle { /// Draw a frame inside the given `rect` /// /// The frame dimensions are given by [`SizeHandle::frame`]. - fn frame(&mut self, rect: Rect, style: FrameStyle, state: InputState); + fn frame(&mut self, id: &WidgetId, rect: Rect, style: FrameStyle); /// Draw a separator in the given `rect` fn separator(&mut self, rect: Rect); @@ -403,7 +399,7 @@ pub trait DrawHandle { /// /// [`SizeMgr::text_bound`] should be called prior to this method to /// select a font, font size and wrap options (based on the [`TextClass`]). - fn text(&mut self, pos: Coord, text: &TextDisplay, class: TextClass, state: InputState); + fn text(&mut self, id: &WidgetId, pos: Coord, text: &TextDisplay, class: TextClass); /// Draw text with effects /// @@ -413,7 +409,7 @@ pub trait DrawHandle { /// /// [`SizeMgr::text_bound`] should be called prior to this method to /// select a font, font size and wrap options (based on the [`TextClass`]). - fn text_effects(&mut self, pos: Coord, text: &dyn TextApi, class: TextClass, state: InputState); + fn text_effects(&mut self, id: &WidgetId, pos: Coord, text: &dyn TextApi, class: TextClass); /// Draw an `AccelString` text /// @@ -421,22 +417,16 @@ pub trait DrawHandle { /// /// [`SizeMgr::text_bound`] should be called prior to this method to /// select a font, font size and wrap options (based on the [`TextClass`]). - fn text_accel( - &mut self, - pos: Coord, - text: &Text, - class: TextClass, - state: InputState, - ); + fn text_accel(&mut self, id: &WidgetId, pos: Coord, text: &Text, class: TextClass); /// Method used to implement [`DrawCtx::text_selected`] fn text_selected_range( &mut self, + id: &WidgetId, pos: Coord, text: &TextDisplay, range: Range, class: TextClass, - state: InputState, ); /// Draw an edit marker at the given `byte` index on this `text` @@ -445,7 +435,7 @@ pub trait DrawHandle { /// select a font, font size and wrap options (based on the [`TextClass`]). fn text_cursor( &mut self, - wid: u64, + id: &WidgetId, pos: Coord, text: &TextDisplay, class: TextClass, @@ -456,19 +446,19 @@ pub trait DrawHandle { /// /// Optionally, a specific colour may be used. // TODO: Allow theme-provided named colours? - fn button(&mut self, rect: Rect, col: Option, state: InputState); + fn button(&mut self, id: &WidgetId, rect: Rect, col: Option); /// Draw UI element: checkbox /// /// The checkbox is a small, usually square, box with or without a check /// mark. A checkbox widget may include a text label, but that label is not /// part of this element. - fn checkbox(&mut self, wid: u64, rect: Rect, checked: bool, state: InputState); + fn checkbox(&mut self, id: &WidgetId, rect: Rect, checked: bool); /// Draw UI element: radiobox /// /// This is similar in appearance to a checkbox. - fn radiobox(&mut self, wid: u64, rect: Rect, checked: bool, state: InputState); + fn radiobox(&mut self, id: &WidgetId, rect: Rect, checked: bool); /// Draw UI element: scrollbar /// @@ -476,7 +466,7 @@ pub trait DrawHandle { /// - `h_rect`: area of slider handle /// - `dir`: direction of bar /// - `state`: highlighting information - fn scrollbar(&mut self, rect: Rect, h_rect: Rect, dir: Direction, state: InputState); + fn scrollbar(&mut self, id: &WidgetId, rect: Rect, h_rect: Rect, dir: Direction); /// Draw UI element: slider /// @@ -484,7 +474,7 @@ pub trait DrawHandle { /// - `h_rect`: area of slider handle /// - `dir`: direction of slider (currently only LTR or TTB) /// - `state`: highlighting information - fn slider(&mut self, rect: Rect, h_rect: Rect, dir: Direction, state: InputState); + fn slider(&mut self, id: &WidgetId, rect: Rect, h_rect: Rect, dir: Direction); /// Draw UI element: progress bar /// @@ -492,7 +482,7 @@ pub trait DrawHandle { /// - `dir`: direction of progress bar /// - `state`: highlighting information /// - `value`: progress value, between 0.0 and 1.0 - fn progress_bar(&mut self, rect: Rect, dir: Direction, state: InputState, value: f32); + fn progress_bar(&mut self, id: &WidgetId, rect: Rect, dir: Direction, value: f32); /// Draw an image fn image(&mut self, id: ImageId, rect: Rect); @@ -519,8 +509,8 @@ macro_rules! impl_ { fn get_clip_rect(&self) -> Rect { self.deref().get_clip_rect() } - fn frame(&mut self, rect: Rect, style: FrameStyle, state: InputState) { - self.deref_mut().frame(rect, style, state); + fn frame(&mut self, id: &WidgetId, rect: Rect, style: FrameStyle) { + self.deref_mut().frame(id, rect, style); } fn separator(&mut self, rect: Rect) { self.deref_mut().separator(rect); @@ -528,58 +518,55 @@ macro_rules! impl_ { fn selection_box(&mut self, rect: Rect) { self.deref_mut().selection_box(rect); } - fn text(&mut self, pos: Coord, text: &TextDisplay, class: TextClass, state: InputState) { - self.deref_mut().text(pos, text, class, state) + fn text(&mut self, id: &WidgetId, pos: Coord, text: &TextDisplay, class: TextClass) { + self.deref_mut().text(id, pos, text, class) } fn text_effects( &mut self, - pos: Coord, + id: &WidgetId, pos: Coord, text: &dyn TextApi, class: TextClass, - state: InputState, ) { - self.deref_mut().text_effects(pos, text, class, state); + self.deref_mut().text_effects(id, pos, text, class); } fn text_accel( &mut self, - pos: Coord, + id: &WidgetId, pos: Coord, text: &Text, class: TextClass, - state: InputState, ) { - self.deref_mut().text_accel(pos, text, class, state); + self.deref_mut().text_accel(id, pos, text, class); } fn text_selected_range( &mut self, - pos: Coord, + id: &WidgetId, pos: Coord, text: &TextDisplay, range: Range, class: TextClass, - state: InputState, ) { self.deref_mut() - .text_selected_range(pos, text, range, class, state); + .text_selected_range(id, pos, text, range, class); } - fn text_cursor(&mut self, wid: u64, pos: Coord, text: &TextDisplay, class: TextClass, byte: usize) { - self.deref_mut().text_cursor(wid, pos, text, class, byte) + fn text_cursor(&mut self, id: &WidgetId, pos: Coord, text: &TextDisplay, class: TextClass, byte: usize) { + self.deref_mut().text_cursor(id, pos, text, class, byte) } - fn button(&mut self, rect: Rect, col: Option, state: InputState) { - self.deref_mut().button(rect, col, state) + fn button(&mut self, id: &WidgetId, rect: Rect, col: Option) { + self.deref_mut().button(id, rect, col) } - fn checkbox(&mut self, wid: u64, rect: Rect, checked: bool, state: InputState) { - self.deref_mut().checkbox(wid, rect, checked, state) + fn checkbox(&mut self, id: &WidgetId, rect: Rect, checked: bool) { + self.deref_mut().checkbox(id, rect, checked) } - fn radiobox(&mut self, wid: u64, rect: Rect, checked: bool, state: InputState) { - self.deref_mut().radiobox(wid, rect, checked, state) + fn radiobox(&mut self, id: &WidgetId, rect: Rect, checked: bool) { + self.deref_mut().radiobox(id, rect, checked) } - fn scrollbar(&mut self, rect: Rect, h_rect: Rect, dir: Direction, state: InputState) { - self.deref_mut().scrollbar(rect, h_rect, dir, state) + fn scrollbar(&mut self, id: &WidgetId, rect: Rect, h_rect: Rect, dir: Direction) { + self.deref_mut().scrollbar(id, rect, h_rect, dir) } - fn slider(&mut self, rect: Rect, h_rect: Rect, dir: Direction, state: InputState) { - self.deref_mut().slider(rect, h_rect, dir, state) + fn slider(&mut self, id: &WidgetId, rect: Rect, h_rect: Rect, dir: Direction) { + self.deref_mut().slider(id, rect, h_rect, dir) } - fn progress_bar(&mut self, rect: Rect, dir: Direction, state: InputState, value: f32) { - self.deref_mut().progress_bar(rect, dir, state, value); + fn progress_bar(&mut self, id: &WidgetId, rect: Rect, dir: Direction, value: f32) { + self.deref_mut().progress_bar(id, rect, dir, value); } fn image(&mut self, id: ImageId, rect: Rect) { self.deref_mut().image(id, rect); @@ -604,7 +591,7 @@ mod test { // But we don't need to: we just want to test that methods are callable. let _scale = draw.size_mgr().scale_factor(); - let mut draw = draw.with_id(&WidgetId::ROOT); + let mut draw = draw.with_id(WidgetId::ROOT); let text = crate::text::Text::new_single("sample"); let class = TextClass::Label; diff --git a/crates/kas-core/src/theme/mod.rs b/crates/kas-core/src/theme/mod.rs index b2dd16e5b..f8387a6dc 100644 --- a/crates/kas-core/src/theme/mod.rs +++ b/crates/kas-core/src/theme/mod.rs @@ -18,80 +18,6 @@ use crate::event::EventMgr; use crate::TkAction; use std::ops::{Deref, DerefMut}; -bitflags! { - /// Input and highlighting state of a widget - /// - /// This struct is used to adjust the appearance of [`DrawMgr`]'s primitives. - #[derive(Default)] - pub struct InputState: u8 { - /// Disabled widgets are not responsive to input and usually drawn in grey. - /// - /// All other states should be ignored when disabled. - const DISABLED = 1 << 0; - /// Some widgets, such as `EditBox`, use a red background on error - const ERROR = 1 << 1; - /// "Hover" is true if the mouse is over this element - const HOVER = 1 << 2; - /// Elements such as buttons, handles and menu entries may be depressed - /// (visually pushed) by a click or touch event or an accelerator key. - /// This is often visualised by a darker colour and/or by offsetting - /// graphics. The `hover` state should be ignored when depressed. - const DEPRESS = 1 << 3; - /// Keyboard navigation of UIs moves a "focus" from widget to widget. - const NAV_FOCUS = 1 << 4; - /// "Character focus" implies this widget is ready to receive text input - /// (e.g. typing into an input field). - const CHAR_FOCUS = 1 << 5; - /// "Selection focus" allows things such as text to be selected. Selection - /// focus implies that the widget also has character focus. - const SEL_FOCUS = 1 << 6; - } -} - -impl InputState { - /// Extract `DISABLED` bit - #[inline] - pub fn disabled(self) -> bool { - self.contains(InputState::DISABLED) - } - - /// Extract `ERROR` bit - #[inline] - pub fn error(self) -> bool { - self.contains(InputState::ERROR) - } - - /// Extract `HOVER` bit - #[inline] - pub fn hover(self) -> bool { - self.contains(InputState::HOVER) - } - - /// Extract `DEPRESS` bit - #[inline] - pub fn depress(self) -> bool { - self.contains(InputState::DEPRESS) - } - - /// Extract `NAV_FOCUS` bit - #[inline] - pub fn nav_focus(self) -> bool { - self.contains(InputState::NAV_FOCUS) - } - - /// Extract `CHAR_FOCUS` bit - #[inline] - pub fn char_focus(self) -> bool { - self.contains(InputState::CHAR_FOCUS) - } - - /// Extract `SEL_FOCUS` bit - #[inline] - pub fn sel_focus(self) -> bool { - self.contains(InputState::SEL_FOCUS) - } -} - /// Interface through which a theme can be adjusted at run-time /// /// All methods return a [`TkAction`] to enable correct action when a theme diff --git a/crates/kas-resvg/src/canvas.rs b/crates/kas-resvg/src/canvas.rs index 0b086e903..2e131fffb 100644 --- a/crates/kas-resvg/src/canvas.rs +++ b/crates/kas-resvg/src/canvas.rs @@ -138,7 +138,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id_ref()); + let mut draw = draw.with_id(self.id()); let (redraw, animate) = self.program.do_redraw_animate(); if redraw { draw.set_rect_mgr(|mgr| self.redraw(mgr)); diff --git a/crates/kas-resvg/src/svg.rs b/crates/kas-resvg/src/svg.rs index 53d59ef37..6f38cde71 100644 --- a/crates/kas-resvg/src/svg.rs +++ b/crates/kas-resvg/src/svg.rs @@ -185,7 +185,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id_ref()); + let mut draw = draw.with_id(self.id()); if let Some(id) = self.image_id { draw.image(id, self.rect()); } diff --git a/crates/kas-theme/Cargo.toml b/crates/kas-theme/Cargo.toml index d76f16f8e..d4dd5dfdd 100644 --- a/crates/kas-theme/Cargo.toml +++ b/crates/kas-theme/Cargo.toml @@ -46,3 +46,4 @@ linear-map = "1.2.0" log = "0.4" serde = { version = "1.0.123", features = ["derive"], optional = true } stack_dst_ = { version = "0.6", package = "stack_dst", optional = true } +bitflags = "1.3.1" diff --git a/crates/kas-theme/src/anim.rs b/crates/kas-theme/src/anim.rs index 3dcee6be0..94a2ce1ad 100644 --- a/crates/kas-theme/src/anim.rs +++ b/crates/kas-theme/src/anim.rs @@ -6,6 +6,7 @@ //! Animation helpers use kas::draw::DrawImpl; +use kas::WidgetId; use std::collections::hash_map::Entry; use std::collections::HashMap; use std::marker::PhantomData; @@ -70,8 +71,8 @@ struct TextCursor { } impl AnimState { /// Flashing text cursor: return true to draw - pub fn text_cursor(&mut self, draw: &mut D, wid: u64, byte: usize) -> bool { - match self.text_cursor.entry(wid) { + pub fn text_cursor(&mut self, draw: &mut D, id: &WidgetId, byte: usize) -> bool { + match self.text_cursor.entry(id.as_u64()) { Entry::Occupied(entry) if entry.get().byte == byte => { let entry = entry.into_mut(); if entry.time < self.now { @@ -112,14 +113,14 @@ impl AnimState { /// Normally returns `1.0` if `state` else `0.0`, but within a short time /// after a state change will linearly transition between these values. #[inline] - pub fn fade_bool(&mut self, draw: &mut D, wid: u64, state: bool) -> f32 { - 1.0 - self.fade_bool_1m(draw, wid, state) + pub fn fade_bool(&mut self, draw: &mut D, id: &WidgetId, state: bool) -> f32 { + 1.0 - self.fade_bool_1m(draw, id, state) } /// `1.0 - self.fade_bool(..)` - pub fn fade_bool_1m(&mut self, draw: &mut D, wid: u64, state: bool) -> f32 { + pub fn fade_bool_1m(&mut self, draw: &mut D, id: &WidgetId, state: bool) -> f32 { let mut out_state = state; - match self.fade_bool.entry(wid) { + match self.fade_bool.entry(id.as_u64()) { Entry::Occupied(entry) => { let entry = entry.into_mut(); entry.time = self.now; diff --git a/crates/kas-theme/src/colors.rs b/crates/kas-theme/src/colors.rs index 76039f6b1..a6e460023 100644 --- a/crates/kas-theme/src/colors.rs +++ b/crates/kas-theme/src/colors.rs @@ -6,13 +6,111 @@ //! Colour schemes use kas::draw::color::{Rgba, Rgba8Srgb}; -use kas::theme::InputState; +use kas::event::EventState; +use kas::WidgetId; use std::str::FromStr; const MULT_DEPRESS: f32 = 0.75; const MULT_HIGHLIGHT: f32 = 1.25; const MIN_HIGHLIGHT: f32 = 0.2; +bitflags::bitflags! { + /// Input and highlighting state of a widget + /// + /// This struct is used to adjust the appearance of [`DrawMgr`]'s primitives. + #[derive(Default)] + pub struct InputState: u8 { + /// Disabled widgets are not responsive to input and usually drawn in grey. + /// + /// All other states should be ignored when disabled. + const DISABLED = 1 << 0; + /// "Hover" is true if the mouse is over this element + const HOVER = 1 << 2; + /// Elements such as buttons, handles and menu entries may be depressed + /// (visually pushed) by a click or touch event or an accelerator key. + /// This is often visualised by a darker colour and/or by offsetting + /// graphics. The `hover` state should be ignored when depressed. + const DEPRESS = 1 << 3; + /// Keyboard navigation of UIs moves a "focus" from widget to widget. + const NAV_FOCUS = 1 << 4; + /// "Character focus" implies this widget is ready to receive text input + /// (e.g. typing into an input field). + const CHAR_FOCUS = 1 << 5; + /// "Selection focus" allows things such as text to be selected. Selection + /// focus implies that the widget also has character focus. + const SEL_FOCUS = 1 << 6; + } +} + +impl InputState { + /// Construct, setting all components + pub fn new_all(ev: &EventState, id: &WidgetId) -> Self { + let mut state = Self::new_except_depress(ev, id); + if ev.is_depressed(id) { + state |= InputState::DEPRESS; + } + state + } + + /// Construct, setting all but depress status + pub fn new_except_depress(ev: &EventState, id: &WidgetId) -> Self { + let (char_focus, sel_focus) = ev.has_char_focus(id); + let mut state = InputState::empty(); + if ev.is_disabled(id) { + state |= InputState::DISABLED; + } + if ev.is_hovered(id) { + state |= InputState::HOVER; + } + if ev.has_nav_focus(id) { + state |= InputState::NAV_FOCUS; + } + if char_focus { + state |= InputState::CHAR_FOCUS; + } + if sel_focus { + state |= InputState::SEL_FOCUS; + } + state + } + + /// Extract `DISABLED` bit + #[inline] + pub fn disabled(self) -> bool { + self.contains(InputState::DISABLED) + } + + /// Extract `HOVER` bit + #[inline] + pub fn hover(self) -> bool { + self.contains(InputState::HOVER) + } + + /// Extract `DEPRESS` bit + #[inline] + pub fn depress(self) -> bool { + self.contains(InputState::DEPRESS) + } + + /// Extract `NAV_FOCUS` bit + #[inline] + pub fn nav_focus(self) -> bool { + self.contains(InputState::NAV_FOCUS) + } + + /// Extract `CHAR_FOCUS` bit + #[inline] + pub fn char_focus(self) -> bool { + self.contains(InputState::CHAR_FOCUS) + } + + /// Extract `SEL_FOCUS` bit + #[inline] + pub fn sel_focus(self) -> bool { + self.contains(InputState::SEL_FOCUS) + } +} + /// Provides standard theme colours #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "config", derive(serde::Serialize, serde::Deserialize))] @@ -189,10 +287,10 @@ impl ColorsLinear { } /// Get colour of a text area, depending on state - pub fn edit_bg(&self, state: InputState) -> Rgba { + pub fn edit_bg(&self, state: InputState, state_error: bool) -> Rgba { let mut col = if state.disabled() { self.edit_bg_disabled - } else if state.error() { + } else if state_error { self.edit_bg_error } else { self.edit_bg diff --git a/crates/kas-theme/src/flat_theme.rs b/crates/kas-theme/src/flat_theme.rs index 35e5a4370..5e1f59e1a 100644 --- a/crates/kas-theme/src/flat_theme.rs +++ b/crates/kas-theme/src/flat_theme.rs @@ -12,7 +12,7 @@ use std::f32; use std::ops::Range; use std::rc::Rc; -use crate::{dim, ColorsLinear, Config, Theme}; +use crate::{dim, ColorsLinear, Config, InputState, Theme}; use kas::cast::traits::*; use kas::dir::{Direction, Directional}; use kas::draw::{color::Rgba, *}; @@ -20,9 +20,9 @@ use kas::event::EventState; use kas::geom::*; use kas::text::format::FormattableText; use kas::text::{fonts, AccelString, Effect, Text, TextApi, TextDisplay}; -use kas::theme::{self, InputState, SizeHandle, ThemeControl}; +use kas::theme::{self, SizeHandle, ThemeControl}; use kas::theme::{FrameStyle, TextClass}; -use kas::TkAction; +use kas::{TkAction, WidgetId}; // Used to ensure a rectangular background is inside a circular corner. // Also the maximum inner radius of circular borders to overlap with this rect. @@ -288,9 +288,9 @@ where inner } - fn edit_box(&mut self, outer: Quad, mut state: InputState) { - state.remove(InputState::DEPRESS); - let col_bg = self.cols.edit_bg(state); + fn edit_box(&mut self, id: &WidgetId, outer: Quad, state_error: bool) { + let state = InputState::new_except_depress(self.ev, id); + let col_bg = self.cols.edit_bg(state, state_error); if col_bg != self.cols.background { let inner = outer.shrink(self.w.dims.button_frame as f32 * BG_SHRINK_FACTOR); self.draw.rect(inner, col_bg); @@ -375,7 +375,7 @@ where self.draw.get_clip_rect() } - fn frame(&mut self, rect: Rect, style: FrameStyle, state: InputState) { + fn frame(&mut self, id: &WidgetId, rect: Rect, style: FrameStyle) { let outer = Quad::conv(rect); match style { FrameStyle::InnerMargin => (), @@ -396,6 +396,7 @@ where self.draw.rect(inner, self.cols.background); } FrameStyle::MenuEntry => { + let state = InputState::new_all(self.ev, id); if let Some(col) = self.cols.menu_entry(state) { let size = self.w.dims.menu_frame as f32; let inner = outer.shrink(size); @@ -405,13 +406,14 @@ where } } FrameStyle::NavFocus => { + let state = InputState::new_all(self.ev, id); if let Some(col) = self.cols.nav_region(state) { let inner = outer.shrink(self.w.dims.inner_margin as f32); self.draw.rounded_frame(outer, inner, 0.0, col); } } - FrameStyle::Button => self.button(rect, None, state), - FrameStyle::EditBox => self.edit_box(outer, state), + FrameStyle::Button => self.button(id, rect, None), + FrameStyle::EditBox => self.edit_box(id, outer, false), } } @@ -428,8 +430,8 @@ where self.draw.frame(outer, inner, col); } - fn text(&mut self, pos: Coord, text: &TextDisplay, _: TextClass, state: InputState) { - let col = if state.disabled() { + fn text(&mut self, id: &WidgetId, pos: Coord, text: &TextDisplay, _: TextClass) { + let col = if self.ev.is_disabled(id) { self.cols.text_disabled } else { self.cols.text @@ -437,8 +439,8 @@ where self.draw.text(pos.cast(), text, col); } - fn text_effects(&mut self, pos: Coord, text: &dyn TextApi, _: TextClass, state: InputState) { - let col = if state.disabled() { + fn text_effects(&mut self, id: &WidgetId, pos: Coord, text: &dyn TextApi, _: TextClass) { + let col = if self.ev.is_disabled(id) { self.cols.text_disabled } else { self.cols.text @@ -447,15 +449,9 @@ where .text_col_effects(pos.cast(), text.display(), col, text.effect_tokens()); } - fn text_accel( - &mut self, - pos: Coord, - text: &Text, - _: TextClass, - state: InputState, - ) { + fn text_accel(&mut self, id: &WidgetId, pos: Coord, text: &Text, _: TextClass) { let pos = Vec2::conv(pos); - let col = if state.disabled() { + let col = if self.ev.is_disabled(id) { self.cols.text_disabled } else { self.cols.text @@ -470,14 +466,14 @@ where fn text_selected_range( &mut self, + id: &WidgetId, pos: Coord, text: &TextDisplay, range: Range, _: TextClass, - state: InputState, ) { let pos = Vec2::conv(pos); - let col = if state.disabled() { + let col = if self.ev.is_disabled(id) { self.cols.text_disabled } else { self.cols.text @@ -512,8 +508,15 @@ where self.draw.text_effects(pos, text, &effects); } - fn text_cursor(&mut self, wid: u64, pos: Coord, text: &TextDisplay, _: TextClass, byte: usize) { - if !self.w.anim.text_cursor(self.draw.draw, wid, byte) { + fn text_cursor( + &mut self, + id: &WidgetId, + pos: Coord, + text: &TextDisplay, + _: TextClass, + byte: usize, + ) { + if !self.w.anim.text_cursor(self.draw.draw, id, byte) { return; } @@ -545,7 +548,8 @@ where } } - fn button(&mut self, rect: Rect, col: Option, state: InputState) { + fn button(&mut self, id: &WidgetId, rect: Rect, col: Option) { + let state = InputState::new_all(self.ev, id); let outer = Quad::conv(rect); let col_bg = if state.depress() || state.nav_focus() && !state.disabled() { @@ -558,13 +562,14 @@ where self.button_frame(outer, col_frame, col_bg, state); } - fn checkbox(&mut self, wid: u64, rect: Rect, checked: bool, state: InputState) { - let anim_fade = self.w.anim.fade_bool_1m(self.draw.draw, wid, checked); + fn checkbox(&mut self, id: &WidgetId, rect: Rect, checked: bool) { + let anim_fade = self.w.anim.fade_bool_1m(self.draw.draw, id, checked); + let state = InputState::new_all(self.ev, id); let outer = Quad::conv(rect); let col_frame = self.cols.nav_region(state).unwrap_or(self.cols.frame); - let inner = self.button_frame(outer, col_frame, self.cols.edit_bg(state), state); + let inner = self.button_frame(outer, col_frame, self.cols.edit_bg(state, false), state); if anim_fade < 1.0 { let inner = inner.shrink((2 * self.w.dims.inner_margin) as f32); @@ -575,9 +580,10 @@ where } } - fn radiobox(&mut self, wid: u64, rect: Rect, checked: bool, state: InputState) { - let anim_fade = self.w.anim.fade_bool_1m(self.draw.draw, wid, checked); + fn radiobox(&mut self, id: &WidgetId, rect: Rect, checked: bool) { + let anim_fade = self.w.anim.fade_bool_1m(self.draw.draw, id, checked); + let state = InputState::new_all(self.ev, id); let outer = Quad::conv(rect); let col = self.cols.nav_region(state).unwrap_or(self.cols.frame); @@ -596,7 +602,8 @@ where self.draw.circle_2col(shadow_outer, col1, col2); } - self.draw.circle(outer, 0.0, self.cols.edit_bg(state)); + self.draw + .circle(outer, 0.0, self.cols.edit_bg(state, false)); const F: f32 = 2.0 * (1.0 - BG_SHRINK_FACTOR); // match checkbox frame let r = 1.0 - F * self.w.dims.button_frame as f32 / rect.size.0 as f32; @@ -612,7 +619,7 @@ where } } - fn scrollbar(&mut self, rect: Rect, h_rect: Rect, _dir: Direction, state: InputState) { + fn scrollbar(&mut self, id: &WidgetId, rect: Rect, h_rect: Rect, _dir: Direction) { // track let outer = Quad::conv(rect); let inner = outer.shrink(outer.size().min_comp() / 2.0); @@ -625,7 +632,7 @@ where let r = outer.size().min_comp() * 0.125; let outer = outer.shrink(r); let inner = outer.shrink(3.0 * r); - let col = if state.depress() || state.nav_focus() { + let col = if self.ev.is_depressed(id) || self.ev.has_nav_focus(id) { self.cols.nav_focus } else { self.cols.accent_soft @@ -633,7 +640,9 @@ where self.draw.rounded_frame(outer, inner, 0.0, col); } - fn slider(&mut self, rect: Rect, h_rect: Rect, dir: Direction, state: InputState) { + fn slider(&mut self, id: &WidgetId, rect: Rect, h_rect: Rect, dir: Direction) { + let state = InputState::new_all(self.ev, id); + // track let mut outer = Quad::conv(rect); let mid = Vec2::conv(h_rect.pos + h_rect.size / 2); @@ -701,7 +710,7 @@ where self.draw.circle(outer, 14.0 / 16.0, col); } - fn progress_bar(&mut self, rect: Rect, dir: Direction, _: InputState, value: f32) { + fn progress_bar(&mut self, _: &WidgetId, rect: Rect, dir: Direction, value: f32) { let mut outer = Quad::conv(rect); let inner = outer.shrink(outer.size().min_comp() / 2.0); self.draw.rounded_frame(outer, inner, 0.75, self.cols.frame); diff --git a/crates/kas-theme/src/lib.rs b/crates/kas-theme/src/lib.rs index 64a93eaa8..a3eaed661 100644 --- a/crates/kas-theme/src/lib.rs +++ b/crates/kas-theme/src/lib.rs @@ -32,6 +32,7 @@ mod traits; pub mod dim; +pub(crate) use colors::InputState; pub use colors::{Colors, ColorsLinear, ColorsSrgb}; pub use config::{Config, RasterConfig}; pub use draw_shaded::{DrawShaded, DrawShadedImpl}; diff --git a/crates/kas-theme/src/shaded_theme.rs b/crates/kas-theme/src/shaded_theme.rs index 51f4c1d6b..717d5c573 100644 --- a/crates/kas-theme/src/shaded_theme.rs +++ b/crates/kas-theme/src/shaded_theme.rs @@ -8,7 +8,7 @@ use std::f32; use std::ops::Range; -use crate::{dim, ColorsLinear, Config, FlatTheme, Theme}; +use crate::{dim, ColorsLinear, Config, FlatTheme, InputState, Theme}; use crate::{DrawShaded, DrawShadedImpl}; use kas::cast::traits::*; use kas::dir::{Direction, Directional}; @@ -16,9 +16,9 @@ use kas::draw::{color::Rgba, *}; use kas::event::EventState; use kas::geom::*; use kas::text::{AccelString, Text, TextApi, TextDisplay}; -use kas::theme::{self, InputState, SizeHandle, ThemeControl}; +use kas::theme::{self, SizeHandle, ThemeControl}; use kas::theme::{FrameStyle, TextClass}; -use kas::TkAction; +use kas::{TkAction, WidgetId}; /// A theme using simple shading to give apparent depth to elements #[derive(Clone, Debug)] @@ -286,7 +286,7 @@ where self.draw.get_clip_rect() } - fn frame(&mut self, rect: Rect, style: FrameStyle, mut state: InputState) { + fn frame(&mut self, id: &WidgetId, rect: Rect, style: FrameStyle) { match style { FrameStyle::Frame => { let outer = Quad::conv(rect); @@ -300,18 +300,19 @@ where self.draw.rect(outer, self.cols.background); } FrameStyle::MenuEntry => { + let state = InputState::new_all(self.ev, id); if let Some(col) = self.cols.menu_entry(state) { let outer = Quad::conv(rect); self.draw.rect(outer, col); } } - FrameStyle::Button => self.button(rect, None, state), + FrameStyle::Button => self.button(id, rect, None), FrameStyle::EditBox => { - state.remove(InputState::DEPRESS); - let bg_col = self.cols.edit_bg(state); + let state = InputState::new_except_depress(self.ev, id); + let bg_col = self.cols.edit_bg(state, false); self.draw_edit_box(rect, bg_col, self.cols.nav_region(state)); } - style => self.as_flat().frame(rect, style, state), + style => self.as_flat().frame(id, rect, style), } } @@ -327,54 +328,49 @@ where self.as_flat().selection_box(rect); } - fn text(&mut self, pos: Coord, text: &TextDisplay, class: TextClass, state: InputState) { - self.as_flat().text(pos, text, class, state); + fn text(&mut self, id: &WidgetId, pos: Coord, text: &TextDisplay, class: TextClass) { + self.as_flat().text(id, pos, text, class); } - fn text_effects( - &mut self, - pos: Coord, - text: &dyn TextApi, - class: TextClass, - state: InputState, - ) { - self.as_flat().text_effects(pos, text, class, state); + fn text_effects(&mut self, id: &WidgetId, pos: Coord, text: &dyn TextApi, class: TextClass) { + self.as_flat().text_effects(id, pos, text, class); } fn text_accel( &mut self, + id: &WidgetId, pos: Coord, text: &Text, class: TextClass, - state: InputState, ) { - self.as_flat().text_accel(pos, text, class, state); + self.as_flat().text_accel(id, pos, text, class); } fn text_selected_range( &mut self, + id: &WidgetId, pos: Coord, text: &TextDisplay, range: Range, class: TextClass, - state: InputState, ) { self.as_flat() - .text_selected_range(pos, text, range, class, state); + .text_selected_range(id, pos, text, range, class); } fn text_cursor( &mut self, - wid: u64, + id: &WidgetId, pos: Coord, text: &TextDisplay, class: TextClass, byte: usize, ) { - self.as_flat().text_cursor(wid, pos, text, class, byte); + self.as_flat().text_cursor(id, pos, text, class, byte); } - fn button(&mut self, rect: Rect, col: Option, state: InputState) { + fn button(&mut self, id: &WidgetId, rect: Rect, col: Option) { + let state = InputState::new_all(self.ev, id); let outer = Quad::conv(rect); let inner = outer.shrink(self.w.dims.button_frame as f32); let col = col.map(|c| c.into()).unwrap_or(self.cols.accent_soft); @@ -389,10 +385,11 @@ where } } - fn checkbox(&mut self, wid: u64, rect: Rect, checked: bool, state: InputState) { - let anim_fade = self.w.anim.fade_bool_1m(self.draw.draw, wid, checked); + fn checkbox(&mut self, id: &WidgetId, rect: Rect, checked: bool) { + let state = InputState::new_all(self.ev, id); + let anim_fade = self.w.anim.fade_bool_1m(self.draw.draw, id, checked); - let bg_col = self.cols.edit_bg(state); + let bg_col = self.cols.edit_bg(state, false); let nav_col = self.cols.nav_region(state).or(Some(bg_col)); let inner = self.draw_edit_box(rect, bg_col, nav_col); @@ -405,10 +402,11 @@ where } } - fn radiobox(&mut self, wid: u64, rect: Rect, checked: bool, state: InputState) { - let anim_fade = self.w.anim.fade_bool_1m(self.draw.draw, wid, checked); + fn radiobox(&mut self, id: &WidgetId, rect: Rect, checked: bool) { + let state = InputState::new_all(self.ev, id); + let anim_fade = self.w.anim.fade_bool_1m(self.draw.draw, id, checked); - let bg_col = self.cols.edit_bg(state); + let bg_col = self.cols.edit_bg(state, false); let nav_col = self.cols.nav_region(state).or(Some(bg_col)); let inner = self.draw_edit_box(rect, bg_col, nav_col); @@ -421,7 +419,7 @@ where } } - fn scrollbar(&mut self, rect: Rect, h_rect: Rect, _dir: Direction, state: InputState) { + fn scrollbar(&mut self, id: &WidgetId, rect: Rect, h_rect: Rect, _dir: Direction) { // track let outer = Quad::conv(rect); let inner = outer.shrink(outer.size().min_comp() / 2.0); @@ -430,10 +428,11 @@ where self.draw.shaded_round_frame(outer, inner, norm, col); // handle + let state = InputState::new_all(self.ev, id); self.draw_handle(h_rect, state); } - fn slider(&mut self, rect: Rect, h_rect: Rect, dir: Direction, state: InputState) { + fn slider(&mut self, id: &WidgetId, rect: Rect, h_rect: Rect, dir: Direction) { // track let mut outer = Quad::conv(rect); outer = match dir.is_horizontal() { @@ -446,10 +445,11 @@ where self.draw.shaded_round_frame(outer, inner, norm, col); // handle + let state = InputState::new_all(self.ev, id); self.draw_handle(h_rect, state); } - fn progress_bar(&mut self, rect: Rect, dir: Direction, _: InputState, value: f32) { + fn progress_bar(&mut self, _: &WidgetId, rect: Rect, dir: Direction, value: f32) { let mut outer = Quad::conv(rect); let inner = outer.shrink(outer.size().min_comp() / 2.0); let norm = (0.0, -0.7); diff --git a/crates/kas-widgets/src/checkbox.rs b/crates/kas-widgets/src/checkbox.rs index 8dd3db398..4c4f48da3 100644 --- a/crates/kas-widgets/src/checkbox.rs +++ b/crates/kas-widgets/src/checkbox.rs @@ -41,7 +41,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - draw.with_id(self.id_ref()).checkbox(self.core.rect, self.state); + draw.with_id(self.id()).checkbox(self.core.rect, self.state); } } diff --git a/crates/kas-widgets/src/combobox.rs b/crates/kas-widgets/src/combobox.rs index 09eaa7986..994ebec15 100644 --- a/crates/kas-widgets/src/combobox.rs +++ b/crates/kas-widgets/src/combobox.rs @@ -49,7 +49,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let draw = draw.with_id(self.id_ref()); + let draw = draw.with_id(self.id()); self.layout().draw(draw); } } diff --git a/crates/kas-widgets/src/edit_field.rs b/crates/kas-widgets/src/edit_field.rs index 95ae390ef..df9a4ee97 100644 --- a/crates/kas-widgets/src/edit_field.rs +++ b/crates/kas-widgets/src/edit_field.rs @@ -188,17 +188,9 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id_ref()); - let error = self.inner.has_error(); - { - let mut draw = draw.re(); - let mut draw = draw.with_id(self.inner.id_ref()); - if error { - draw.state.insert(InputState::ERROR); - } - draw.frame(self.core.rect, FrameStyle::EditBox); - } - self.inner.draw(draw.re()); + // FIXME: let error = self.inner.has_error(); + draw.with_id(self.inner.id()).frame(self.core.rect, FrameStyle::EditBox); + self.inner.draw(draw.with_id(self.id()).re()); } } } @@ -421,7 +413,7 @@ widget! { } else { TextClass::Edit }; - let mut draw = draw.with_id(self.id_ref()); + let mut draw = draw.with_id(self.id()); draw.with_clip_region(self.rect(), self.view_offset, |mut draw| { if self.selection.is_empty() { draw.text(self.rect().pos, self.text.as_ref(), class); diff --git a/crates/kas-widgets/src/label.rs b/crates/kas-widgets/src/label.rs index 93e2fc14f..ea30ba7c4 100644 --- a/crates/kas-widgets/src/label.rs +++ b/crates/kas-widgets/src/label.rs @@ -37,12 +37,12 @@ widget! { #[cfg(feature = "min_spec")] default fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id_ref()); + let mut draw = draw.with_id(self.id()); draw.text_effects(self.core.rect.pos, &self.label, TextClass::Label); } #[cfg(not(feature = "min_spec"))] fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id_ref()); + let mut draw = draw.with_id(self.id()); draw.text_effects(self.core.rect.pos, &self.label, TextClass::Label); } } @@ -66,7 +66,7 @@ widget! { #[cfg(feature = "min_spec")] impl Layout for AccelLabel { fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id_ref()); + let mut draw = draw.with_id(self.id()); let accel = draw.ev_state().show_accel_labels(); draw.text_accel(self.core.rect.pos, &self.label, accel, TextClass::Label); } @@ -76,14 +76,14 @@ impl Layout for AccelLabel { #[cfg(feature = "min_spec")] impl<'a> Layout for Label<&'a str> { fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id_ref()); + let mut draw = draw.with_id(self.id()); draw.text(self.core.rect.pos, self.label.as_ref(), TextClass::Label); } } #[cfg(feature = "min_spec")] impl Layout for StringLabel { fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id_ref()); + let mut draw = draw.with_id(self.id()); draw.text(self.core.rect.pos, self.label.as_ref(), TextClass::Label); } } diff --git a/crates/kas-widgets/src/menu/menu_entry.rs b/crates/kas-widgets/src/menu/menu_entry.rs index 2f4ea7f65..12b44f9f1 100644 --- a/crates/kas-widgets/src/menu/menu_entry.rs +++ b/crates/kas-widgets/src/menu/menu_entry.rs @@ -40,7 +40,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id_ref()); + let mut draw = draw.with_id(self.id()); draw.frame(self.core.rect, FrameStyle::MenuEntry); draw.text_accel( self.layout_label.pos, @@ -143,7 +143,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.checkbox.id_ref()); + let mut draw = draw.with_id(self.checkbox.id()); draw.frame(self.core.rect, FrameStyle::MenuEntry); self.layout().draw(draw); } diff --git a/crates/kas-widgets/src/menu/submenu.rs b/crates/kas-widgets/src/menu/submenu.rs index c80fc146d..878226494 100644 --- a/crates/kas-widgets/src/menu/submenu.rs +++ b/crates/kas-widgets/src/menu/submenu.rs @@ -143,7 +143,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id_ref()); + let mut draw = draw.with_id(self.id()); draw.frame(self.core.rect, FrameStyle::MenuEntry); draw.text_accel( self.label_store.pos, diff --git a/crates/kas-widgets/src/progress.rs b/crates/kas-widgets/src/progress.rs index 9bf81fe5b..e16b1f94a 100644 --- a/crates/kas-widgets/src/progress.rs +++ b/crates/kas-widgets/src/progress.rs @@ -100,7 +100,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id_ref()); + let mut draw = draw.with_id(self.id()); let dir = self.direction.as_direction(); draw.progress_bar(self.core.rect, dir, self.value); } diff --git a/crates/kas-widgets/src/radiobox.rs b/crates/kas-widgets/src/radiobox.rs index acfd018fb..95c70c18f 100644 --- a/crates/kas-widgets/src/radiobox.rs +++ b/crates/kas-widgets/src/radiobox.rs @@ -95,7 +95,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id_ref()); + let mut draw = draw.with_id(self.id()); draw.radiobox(self.core.rect, self.state); } } diff --git a/crates/kas-widgets/src/scroll.rs b/crates/kas-widgets/src/scroll.rs index 77582fe6f..c2fd233ad 100644 --- a/crates/kas-widgets/src/scroll.rs +++ b/crates/kas-widgets/src/scroll.rs @@ -131,7 +131,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id_ref()); + let mut draw = draw.with_id(self.id()); draw.with_clip_region(self.core.rect, self.scroll_offset(), |mut draw| { self.inner.draw(draw.re()) }); diff --git a/crates/kas-widgets/src/scroll_label.rs b/crates/kas-widgets/src/scroll_label.rs index d7e8d521f..11c052572 100644 --- a/crates/kas-widgets/src/scroll_label.rs +++ b/crates/kas-widgets/src/scroll_label.rs @@ -54,7 +54,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id_ref()); + let mut draw = draw.with_id(self.id()); let class = TextClass::LabelScroll; draw.with_clip_region(self.rect(), self.view_offset, |mut draw| { if self.selection.is_empty() { diff --git a/crates/kas-widgets/src/scrollbar.rs b/crates/kas-widgets/src/scrollbar.rs index 0a009112f..c675468bf 100644 --- a/crates/kas-widgets/src/scrollbar.rs +++ b/crates/kas-widgets/src/scrollbar.rs @@ -236,7 +236,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.handle.id_ref()); + let mut draw = draw.with_id(self.handle.id()); let dir = self.direction.as_direction(); draw.scrollbar(self.core.rect, self.handle.rect(), dir); } @@ -512,7 +512,7 @@ widget! { } fn draw_(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id_ref()); + let mut draw = draw.with_id(self.id()); if self.show_bars.0 { self.horiz_bar.draw(draw.re()); } @@ -619,7 +619,7 @@ widget! { #[cfg(feature = "min_spec")] impl Layout for ScrollBars> { fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id_ref()); + let mut draw = draw.with_id(self.id()); // Enlarge clip region to *our* rect: draw.with_clip_region(self.core.rect, self.inner.scroll_offset(), |mut draw| { self.inner.inner_mut().draw(draw.re()) diff --git a/crates/kas-widgets/src/separator.rs b/crates/kas-widgets/src/separator.rs index f8faf661b..f8f4a07c7 100644 --- a/crates/kas-widgets/src/separator.rs +++ b/crates/kas-widgets/src/separator.rs @@ -54,7 +54,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id_ref()); + let mut draw = draw.with_id(self.id()); draw.separator(self.core.rect); } } diff --git a/crates/kas-widgets/src/slider.rs b/crates/kas-widgets/src/slider.rs index de96fc221..7844b845a 100644 --- a/crates/kas-widgets/src/slider.rs +++ b/crates/kas-widgets/src/slider.rs @@ -254,10 +254,8 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id_ref()); - if draw.ev_state().is_hovered(self.handle.id_ref()) { - draw.state.insert(InputState::HOVER); - } + let mut draw = draw.with_id(self.id()); + // FIXME (additional hover target): if draw.ev_state().is_hovered(self.handle.id_ref()) let dir = self.direction.as_direction(); draw.slider(self.core.rect, self.handle.rect(), dir); } diff --git a/crates/kas-widgets/src/splitter.rs b/crates/kas-widgets/src/splitter.rs index 9ef330e38..ed06eda4d 100644 --- a/crates/kas-widgets/src/splitter.rs +++ b/crates/kas-widgets/src/splitter.rs @@ -195,7 +195,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id_ref()); + let mut draw = draw.with_id(self.id()); // as with find_id, there's not much harm in invoking the solver twice let solver = layout::RowPositionSolver::new(self.direction); diff --git a/crates/kas-widgets/src/sprite.rs b/crates/kas-widgets/src/sprite.rs index f87fdee56..ff47dbc20 100644 --- a/crates/kas-widgets/src/sprite.rs +++ b/crates/kas-widgets/src/sprite.rs @@ -49,7 +49,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id_ref()); + let mut draw = draw.with_id(self.id()); if let Some(id) = self.id { draw.image(id, self.rect()); } diff --git a/crates/kas-widgets/src/stack.rs b/crates/kas-widgets/src/stack.rs index 86e334e2b..b743ec825 100644 --- a/crates/kas-widgets/src/stack.rs +++ b/crates/kas-widgets/src/stack.rs @@ -79,7 +79,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id_ref()); + let mut draw = draw.with_id(self.id()); if self.active < self.widgets.len() { self.widgets[self.active].draw(draw.re()); } diff --git a/crates/kas-widgets/src/view/list_view.rs b/crates/kas-widgets/src/view/list_view.rs index 6d190eabc..6dd8d5d6e 100644 --- a/crates/kas-widgets/src/view/list_view.rs +++ b/crates/kas-widgets/src/view/list_view.rs @@ -592,7 +592,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id_ref()); + let mut draw = draw.with_id(self.id()); let offset = self.scroll_offset(); draw.with_clip_region(self.core.rect, offset, |mut draw| { for child in &mut self.widgets[..self.cur_len.cast()] { diff --git a/crates/kas-widgets/src/view/matrix_view.rs b/crates/kas-widgets/src/view/matrix_view.rs index 54245e6e5..71741c7a8 100644 --- a/crates/kas-widgets/src/view/matrix_view.rs +++ b/crates/kas-widgets/src/view/matrix_view.rs @@ -586,7 +586,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id_ref()); + let mut draw = draw.with_id(self.id()); let offset = self.scroll_offset(); let rect = self.rect() + offset; let num = self.cur_len.cast(); diff --git a/crates/kas-widgets/src/window.rs b/crates/kas-widgets/src/window.rs index eb00f744f..7744f3442 100644 --- a/crates/kas-widgets/src/window.rs +++ b/crates/kas-widgets/src/window.rs @@ -50,7 +50,7 @@ widget! { #[inline] fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id_ref()); + let mut draw = draw.with_id(self.id()); self.w.draw(draw.re()); for (_, popup) in &self.popups { if let Some(widget) = self.w.find_widget_mut(&popup.id) { From 142b2e76b0d8bb4d392812fce7828a29a2eb58cf Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 7 Mar 2022 07:14:54 +0000 Subject: [PATCH 10/16] Pause theme animation when window loses focus --- crates/kas-core/src/event/manager.rs | 1 + crates/kas-core/src/event/manager/mgr_pub.rs | 6 ++++++ crates/kas-core/src/event/manager/mgr_shell.rs | 15 +++++++++++---- crates/kas-theme/src/flat_theme.rs | 2 +- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/crates/kas-core/src/event/manager.rs b/crates/kas-core/src/event/manager.rs index aa7cd8940..46f2eb6fb 100644 --- a/crates/kas-core/src/event/manager.rs +++ b/crates/kas-core/src/event/manager.rs @@ -150,6 +150,7 @@ pub struct EventState { scale_factor: f32, configure_active: bool, configure_count: usize, + window_has_focus: bool, modifiers: ModifiersState, /// char focus is on same widget as sel_focus; otherwise its value is ignored char_focus: bool, diff --git a/crates/kas-core/src/event/manager/mgr_pub.rs b/crates/kas-core/src/event/manager/mgr_pub.rs index 7ff0da7f9..b01c230d4 100644 --- a/crates/kas-core/src/event/manager/mgr_pub.rs +++ b/crates/kas-core/src/event/manager/mgr_pub.rs @@ -28,6 +28,12 @@ impl<'a> std::ops::BitOrAssign for EventMgr<'a> { /// Public API (around event manager state) impl EventState { + /// True when the window has focus + #[inline] + pub fn window_has_focus(&self) -> bool { + self.window_has_focus + } + /// True when accelerator key labels should be shown /// /// (True when Alt is held and no widget has character focus.) diff --git a/crates/kas-core/src/event/manager/mgr_shell.rs b/crates/kas-core/src/event/manager/mgr_shell.rs index 85b2d4298..b830ace21 100644 --- a/crates/kas-core/src/event/manager/mgr_shell.rs +++ b/crates/kas-core/src/event/manager/mgr_shell.rs @@ -36,6 +36,7 @@ impl EventState { scale_factor, configure_active: false, configure_count: 0, + window_has_focus: false, modifiers: ModifiersState::empty(), char_focus: false, sel_focus: None, @@ -344,10 +345,16 @@ impl<'a> EventMgr<'a> { } } } - Focused(false) => { - // Window focus lost: close all popups - while let Some(id) = self.state.popups.last().map(|(id, _, _)| *id) { - self.close_window(id, true); + Focused(state) => { + self.state.window_has_focus = state; + if state { + // Required to restart theme animations + self.state.send_action(TkAction::REDRAW); + } else { + // Window focus lost: close all popups + while let Some(id) = self.state.popups.last().map(|(id, _, _)| *id) { + self.close_window(id, true); + } } } KeyboardInput { diff --git a/crates/kas-theme/src/flat_theme.rs b/crates/kas-theme/src/flat_theme.rs index 5e1f59e1a..596810c80 100644 --- a/crates/kas-theme/src/flat_theme.rs +++ b/crates/kas-theme/src/flat_theme.rs @@ -516,7 +516,7 @@ where _: TextClass, byte: usize, ) { - if !self.w.anim.text_cursor(self.draw.draw, id, byte) { + if self.ev.window_has_focus() && !self.w.anim.text_cursor(self.draw.draw, id, byte) { return; } From d55759ae7ff8f10971bd29d25c4a6603d035c524 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 7 Mar 2022 09:01:53 +0000 Subject: [PATCH 11/16] Fix highlighting of scrollbar and slider --- crates/kas-core/src/theme/draw.rs | 39 +++++++++++++++++----------- crates/kas-theme/src/colors.rs | 9 +++++++ crates/kas-theme/src/flat_theme.rs | 13 ++++------ crates/kas-theme/src/shaded_theme.rs | 8 +++--- crates/kas-widgets/src/scrollbar.rs | 7 +++-- crates/kas-widgets/src/slider.rs | 3 +-- 6 files changed, 48 insertions(+), 31 deletions(-) diff --git a/crates/kas-core/src/theme/draw.rs b/crates/kas-core/src/theme/draw.rs index 9bdd7514f..ea88d7961 100644 --- a/crates/kas-core/src/theme/draw.rs +++ b/crates/kas-core/src/theme/draw.rs @@ -301,22 +301,22 @@ impl<'a> DrawCtx<'a> { /// Draw UI element: scrollbar /// + /// - `id2`: [`WidgetId`] of the handle /// - `rect`: area of whole widget (slider track) /// - `h_rect`: area of slider handle /// - `dir`: direction of bar - /// - `state`: highlighting information - pub fn scrollbar(&mut self, rect: Rect, h_rect: Rect, dir: Direction) { - self.h.scrollbar(&self.id, rect, h_rect, dir); + pub fn scrollbar(&mut self, id2: &WidgetId, rect: Rect, h_rect: Rect, dir: Direction) { + self.h.scrollbar(&self.id, id2, rect, h_rect, dir); } /// Draw UI element: slider /// + /// - `id2`: [`WidgetId`] of the handle /// - `rect`: area of whole widget (slider track) /// - `h_rect`: area of slider handle /// - `dir`: direction of slider (currently only LTR or TTB) - /// - `state`: highlighting information - pub fn slider(&mut self, rect: Rect, h_rect: Rect, dir: Direction) { - self.h.slider(&self.id, rect, h_rect, dir); + pub fn slider(&mut self, id2: &WidgetId, rect: Rect, h_rect: Rect, dir: Direction) { + self.h.slider(&self.id, id2, rect, h_rect, dir); } /// Draw UI element: progress bar @@ -462,25 +462,34 @@ pub trait DrawHandle { /// Draw UI element: scrollbar /// + /// - `id`: [`WidgetId`] of the bar + /// - `id2`: [`WidgetId`] of the handle /// - `rect`: area of whole widget (slider track) /// - `h_rect`: area of slider handle /// - `dir`: direction of bar - /// - `state`: highlighting information - fn scrollbar(&mut self, id: &WidgetId, rect: Rect, h_rect: Rect, dir: Direction); + fn scrollbar( + &mut self, + id: &WidgetId, + id2: &WidgetId, + rect: Rect, + h_rect: Rect, + dir: Direction, + ); /// Draw UI element: slider /// + /// - `id`: [`WidgetId`] of the bar + /// - `id2`: [`WidgetId`] of the handle /// - `rect`: area of whole widget (slider track) /// - `h_rect`: area of slider handle /// - `dir`: direction of slider (currently only LTR or TTB) - /// - `state`: highlighting information - fn slider(&mut self, id: &WidgetId, rect: Rect, h_rect: Rect, dir: Direction); + fn slider(&mut self, id: &WidgetId, id2: &WidgetId, rect: Rect, h_rect: Rect, dir: Direction); /// Draw UI element: progress bar /// + /// - `id`: [`WidgetId`] of the bar /// - `rect`: area of whole widget /// - `dir`: direction of progress bar - /// - `state`: highlighting information /// - `value`: progress value, between 0.0 and 1.0 fn progress_bar(&mut self, id: &WidgetId, rect: Rect, dir: Direction, value: f32); @@ -559,11 +568,11 @@ macro_rules! impl_ { fn radiobox(&mut self, id: &WidgetId, rect: Rect, checked: bool) { self.deref_mut().radiobox(id, rect, checked) } - fn scrollbar(&mut self, id: &WidgetId, rect: Rect, h_rect: Rect, dir: Direction) { - self.deref_mut().scrollbar(id, rect, h_rect, dir) + fn scrollbar(&mut self, id: &WidgetId, id2: &WidgetId, rect: Rect, h_rect: Rect, dir: Direction) { + self.deref_mut().scrollbar(id, id2, rect, h_rect, dir) } - fn slider(&mut self, id: &WidgetId, rect: Rect, h_rect: Rect, dir: Direction) { - self.deref_mut().slider(id, rect, h_rect, dir) + fn slider(&mut self, id: &WidgetId, id2: &WidgetId, rect: Rect, h_rect: Rect, dir: Direction) { + self.deref_mut().slider(id, id2, rect, h_rect, dir) } fn progress_bar(&mut self, id: &WidgetId, rect: Rect, dir: Direction, value: f32) { self.deref_mut().progress_bar(id, rect, dir, value); diff --git a/crates/kas-theme/src/colors.rs b/crates/kas-theme/src/colors.rs index a6e460023..8c1b9c949 100644 --- a/crates/kas-theme/src/colors.rs +++ b/crates/kas-theme/src/colors.rs @@ -74,6 +74,15 @@ impl InputState { state } + /// Construct, setting all components, also setting hover from `id2` + pub fn new2(ev: &EventState, id: &WidgetId, id2: &WidgetId) -> Self { + let mut state = Self::new_all(ev, id); + if ev.is_hovered(id2) { + state |= InputState::HOVER; + } + state + } + /// Extract `DISABLED` bit #[inline] pub fn disabled(self) -> bool { diff --git a/crates/kas-theme/src/flat_theme.rs b/crates/kas-theme/src/flat_theme.rs index 596810c80..3f9fe8f65 100644 --- a/crates/kas-theme/src/flat_theme.rs +++ b/crates/kas-theme/src/flat_theme.rs @@ -619,7 +619,7 @@ where } } - fn scrollbar(&mut self, id: &WidgetId, rect: Rect, h_rect: Rect, _dir: Direction) { + fn scrollbar(&mut self, id: &WidgetId, id2: &WidgetId, rect: Rect, h_rect: Rect, _: Direction) { // track let outer = Quad::conv(rect); let inner = outer.shrink(outer.size().min_comp() / 2.0); @@ -632,16 +632,13 @@ where let r = outer.size().min_comp() * 0.125; let outer = outer.shrink(r); let inner = outer.shrink(3.0 * r); - let col = if self.ev.is_depressed(id) || self.ev.has_nav_focus(id) { - self.cols.nav_focus - } else { - self.cols.accent_soft - }; + let state = InputState::new2(self.ev, id, id2); + let col = self.cols.accent_soft_state(state); self.draw.rounded_frame(outer, inner, 0.0, col); } - fn slider(&mut self, id: &WidgetId, rect: Rect, h_rect: Rect, dir: Direction) { - let state = InputState::new_all(self.ev, id); + fn slider(&mut self, id: &WidgetId, id2: &WidgetId, rect: Rect, h_rect: Rect, dir: Direction) { + let state = InputState::new2(self.ev, id, id2); // track let mut outer = Quad::conv(rect); diff --git a/crates/kas-theme/src/shaded_theme.rs b/crates/kas-theme/src/shaded_theme.rs index 717d5c573..f9e885c64 100644 --- a/crates/kas-theme/src/shaded_theme.rs +++ b/crates/kas-theme/src/shaded_theme.rs @@ -419,7 +419,7 @@ where } } - fn scrollbar(&mut self, id: &WidgetId, rect: Rect, h_rect: Rect, _dir: Direction) { + fn scrollbar(&mut self, id: &WidgetId, id2: &WidgetId, rect: Rect, h_rect: Rect, _: Direction) { // track let outer = Quad::conv(rect); let inner = outer.shrink(outer.size().min_comp() / 2.0); @@ -428,11 +428,11 @@ where self.draw.shaded_round_frame(outer, inner, norm, col); // handle - let state = InputState::new_all(self.ev, id); + let state = InputState::new2(self.ev, id, id2); self.draw_handle(h_rect, state); } - fn slider(&mut self, id: &WidgetId, rect: Rect, h_rect: Rect, dir: Direction) { + fn slider(&mut self, id: &WidgetId, id2: &WidgetId, rect: Rect, h_rect: Rect, dir: Direction) { // track let mut outer = Quad::conv(rect); outer = match dir.is_horizontal() { @@ -445,7 +445,7 @@ where self.draw.shaded_round_frame(outer, inner, norm, col); // handle - let state = InputState::new_all(self.ev, id); + let state = InputState::new2(self.ev, id, id2); self.draw_handle(h_rect, state); } diff --git a/crates/kas-widgets/src/scrollbar.rs b/crates/kas-widgets/src/scrollbar.rs index c675468bf..c9d946c8f 100644 --- a/crates/kas-widgets/src/scrollbar.rs +++ b/crates/kas-widgets/src/scrollbar.rs @@ -17,6 +17,9 @@ widget! { /// and allow the size of the handle to be specified. #[derive(Clone, Debug, Default)] #[handler(msg = i32)] + #[widget{ + hover_highlight = true; + }] pub struct ScrollBar { #[widget_core] core: CoreData, @@ -236,9 +239,9 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.handle.id()); + let mut draw = draw.with_id(self.id()); let dir = self.direction.as_direction(); - draw.scrollbar(self.core.rect, self.handle.rect(), dir); + draw.scrollbar(self.handle.id_ref(), self.core.rect, self.handle.rect(), dir); } } diff --git a/crates/kas-widgets/src/slider.rs b/crates/kas-widgets/src/slider.rs index 7844b845a..ab42308a9 100644 --- a/crates/kas-widgets/src/slider.rs +++ b/crates/kas-widgets/src/slider.rs @@ -255,9 +255,8 @@ widget! { fn draw(&mut self, mut draw: DrawMgr) { let mut draw = draw.with_id(self.id()); - // FIXME (additional hover target): if draw.ev_state().is_hovered(self.handle.id_ref()) let dir = self.direction.as_direction(); - draw.slider(self.core.rect, self.handle.rect(), dir); + draw.slider(self.handle.id_ref(), self.core.rect, self.handle.rect(), dir); } } From 8f067570c4d79a1535958c915eff701d2749db14 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 7 Mar 2022 11:15:04 +0000 Subject: [PATCH 12/16] Remove DrawCtx; add IdRect and IdCoord --- crates/kas-core/src/core/widget.rs | 13 +- crates/kas-core/src/layout/visitor.rs | 36 ++-- crates/kas-core/src/theme/draw.rs | 238 +++++++++------------ crates/kas-core/src/theme/mod.rs | 51 ++++- crates/kas-core/src/theme/size.rs | 14 +- crates/kas-resvg/src/canvas.rs | 3 +- crates/kas-resvg/src/svg.rs | 3 +- crates/kas-widgets/src/checkbox.rs | 2 +- crates/kas-widgets/src/combobox.rs | 6 +- crates/kas-widgets/src/edit_field.rs | 11 +- crates/kas-widgets/src/label.rs | 15 +- crates/kas-widgets/src/menu/menu_entry.rs | 11 +- crates/kas-widgets/src/menu/submenu.rs | 5 +- crates/kas-widgets/src/progress.rs | 3 +- crates/kas-widgets/src/radiobox.rs | 3 +- crates/kas-widgets/src/scroll.rs | 1 - crates/kas-widgets/src/scroll_label.rs | 5 +- crates/kas-widgets/src/scrollbar.rs | 5 +- crates/kas-widgets/src/separator.rs | 3 +- crates/kas-widgets/src/slider.rs | 3 +- crates/kas-widgets/src/splitter.rs | 3 +- crates/kas-widgets/src/sprite.rs | 3 +- crates/kas-widgets/src/stack.rs | 1 - crates/kas-widgets/src/view/list_view.rs | 3 +- crates/kas-widgets/src/view/matrix_view.rs | 6 +- crates/kas-widgets/src/window.rs | 1 - 26 files changed, 215 insertions(+), 233 deletions(-) diff --git a/crates/kas-core/src/core/widget.rs b/crates/kas-core/src/core/widget.rs index f97d0cc3d..0c7680ce5 100644 --- a/crates/kas-core/src/core/widget.rs +++ b/crates/kas-core/src/core/widget.rs @@ -13,8 +13,6 @@ use crate::event; use crate::event::{EventMgr, EventState}; use crate::geom::{Coord, Offset, Rect}; use crate::layout::{self, AlignHints, AxisInfo, SetRectMgr, SizeRules}; -#[allow(unused)] -use crate::theme::DrawCtx; use crate::theme::{DrawMgr, SizeMgr}; use crate::util::IdentifyWidget; use crate::{CoreData, WidgetId}; @@ -302,7 +300,7 @@ pub trait Layout: WidgetChildren { /// /// Affects event handling via [`Self::find_id`] and affects the positioning /// of pop-up menus. [`Self::draw`] must be implemented directly using - /// [`DrawCtx::with_clip_region`] to offset contents. + /// [`DrawMgr::with_clip_region`] to offset contents. #[inline] fn translation(&self) -> Offset { Offset::ZERO @@ -383,13 +381,10 @@ pub trait Layout: WidgetChildren { /// This method is invoked each frame to draw visible widgets. It should /// draw itself and recurse into all visible children. /// - /// One should use `let draw = draw.with_id(self.id());` to obtain - /// a [`DrawCtx`], enabling further drawing. - /// /// The default impl draws elements as defined by [`Self::layout`]. - fn draw(&mut self, mut draw: DrawMgr) { - let draw = draw.with_id(self.id()); - self.layout().draw(draw); + fn draw(&mut self, draw: DrawMgr) { + let id = self.id(); // clone to avoid borrow conflict + self.layout().draw(draw, &id); } } diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index 115d381cd..7acd7194f 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -15,7 +15,7 @@ use crate::cast::Cast; use crate::draw::color::Rgb; use crate::geom::{Coord, Offset, Rect, Size}; use crate::text::{Align, TextApi, TextApiExt}; -use crate::theme::{DrawCtx, FrameStyle, SizeMgr, TextClass}; +use crate::theme::{DrawMgr, FrameStyle, IdCoord, IdRect, SizeMgr, TextClass}; use crate::WidgetId; use crate::{dir::Directional, WidgetConfig}; use std::any::Any; @@ -67,7 +67,7 @@ trait Visitor { fn find_id(&mut self, coord: Coord) -> Option; - fn draw(&mut self, draw: DrawCtx); + fn draw(&mut self, draw: DrawMgr, id: &WidgetId); } /// A layout visitor @@ -296,23 +296,23 @@ impl<'a> Layout<'a> { /// Draw a widget's children #[inline] - pub fn draw(mut self, draw: DrawCtx) { - self.draw_(draw); + pub fn draw(mut self, draw: DrawMgr, id: &WidgetId) { + self.draw_(draw, id); } - fn draw_(&mut self, mut draw: DrawCtx) { + fn draw_(&mut self, mut draw: DrawMgr, id: &WidgetId) { match &mut self.layout { LayoutType::None => (), LayoutType::Single(child) | LayoutType::AlignSingle(child, _) => child.draw(draw.re()), - LayoutType::AlignLayout(layout, _) => layout.draw_(draw), + LayoutType::AlignLayout(layout, _) => layout.draw_(draw, id), LayoutType::Frame(child, storage, style) => { - draw.frame(storage.rect, *style); - child.draw_(draw); + draw.frame(IdRect(id, storage.rect), *style); + child.draw_(draw, id); } LayoutType::Button(child, storage, color) => { - draw.button(storage.rect, *color); - child.draw_(draw); + draw.button(IdRect(id, storage.rect), *color); + child.draw_(draw, id); } - LayoutType::Visitor(layout) => layout.draw(draw), + LayoutType::Visitor(layout) => layout.draw(draw, id), } } } @@ -355,9 +355,9 @@ where self.children.find_map(|child| child.find_id(coord)) } - fn draw(&mut self, mut draw: DrawCtx) { + fn draw(&mut self, mut draw: DrawMgr, id: &WidgetId) { for child in &mut self.children { - child.draw(draw.re_ctx()); + child.draw(draw.re(), id); } } } @@ -399,7 +399,7 @@ impl<'a, W: WidgetConfig, D: Directional> Visitor for Slice<'a, W, D> { .and_then(|child| child.find_id(coord)) } - fn draw(&mut self, mut draw: DrawCtx) { + fn draw(&mut self, mut draw: DrawMgr, _: &WidgetId) { let solver = RowPositionSolver::new(self.direction); solver.for_children(self.children, draw.get_clip_rect(), |w| w.draw(draw.re())); } @@ -441,9 +441,9 @@ where self.children.find_map(|(_, child)| child.find_id(coord)) } - fn draw(&mut self, mut draw: DrawCtx) { + fn draw(&mut self, mut draw: DrawMgr, id: &WidgetId) { for (_, child) in &mut self.children { - child.draw(draw.re_ctx()); + child.draw(draw.re(), id); } } } @@ -504,7 +504,7 @@ impl<'a> Visitor for Text<'a> { None } - fn draw(&mut self, mut draw: DrawCtx) { - draw.text_effects(self.data.pos, self.text, self.class); + fn draw(&mut self, mut draw: DrawMgr, id: &WidgetId) { + draw.text_effects(IdCoord(id, self.data.pos), self.text, self.class); } } diff --git a/crates/kas-core/src/theme/draw.rs b/crates/kas-core/src/theme/draw.rs index ea88d7961..3ce1e82f3 100644 --- a/crates/kas-core/src/theme/draw.rs +++ b/crates/kas-core/src/theme/draw.rs @@ -8,7 +8,7 @@ use std::convert::AsRef; use std::ops::{Bound, Deref, DerefMut, Range, RangeBounds}; -use super::{FrameStyle, SizeHandle, SizeMgr, TextClass}; +use super::{FrameStyle, IdCoord, IdRect, SizeHandle, SizeMgr, TextClass}; use crate::dir::Direction; use crate::draw::{color::Rgb, Draw, DrawShared, ImageId, PassType}; use crate::event::EventState; @@ -28,67 +28,7 @@ pub struct DrawMgr<'a> { } impl<'a> DrawMgr<'a> { - /// Construct from a [`DrawMgr`] and [`EventState`] - #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] - #[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] - pub fn new(h: &'a mut dyn DrawHandle) -> Self { - DrawMgr { h } - } - - /// Access event-management state - pub fn ev_state(&mut self) -> &EventState { - self.h.components().2 - } - - /// Access a [`SizeMgr`] - pub fn size_mgr(&mut self) -> SizeMgr { - SizeMgr::new(self.h.components().0) - } - - /// Access a [`SetRectMgr`] - pub fn set_rect_mgr T, T>(&mut self, mut f: F) -> T { - let (sh, ds, ev) = self.h.components(); - let mut mgr = SetRectMgr::new(sh, ds, ev); - f(&mut mgr) - } - - /// Access a [`DrawShared`] - pub fn draw_shared(&mut self) -> &mut dyn DrawShared { - self.h.components().1 - } - - /// Access the low-level draw device - /// - /// Note: this drawing API is modular, with limited functionality in the - /// base trait [`Draw`]. To access further functionality, it is necessary - /// to downcast with [`crate::draw::DrawIface::downcast_from`]. - pub fn draw_device(&mut self) -> &mut dyn Draw { - self.h.draw_device() - } - - /// Add context to allow draw operations - pub fn with_id<'b>(&'b mut self, id: WidgetId) -> DrawCtx<'b> { - let h = &mut *self.h; - DrawCtx { h, id } - } -} - -/// Draw interface (with context) -/// -/// This interface is provided to widgets in [`crate::Layout::draw`]. -/// Lower-level interfaces may be accessed through [`Self::draw_device`]. -/// -/// Most methods draw some feature. Corresponding size properties may be -/// obtained through a [`SizeMgr`], e.g. through [`Self::size_mgr`]. -pub struct DrawCtx<'a> { - h: &'a mut dyn DrawHandle, - // NOTE: it is a little unfortunate that we need a clone of the WidgetId - // here (a borrow won't work due to borrow conflicts). Still, it's cheap. - id: WidgetId, -} - -impl<'a> DrawCtx<'a> { - /// Reborrow as a [`DrawMgr`] with a new lifetime + /// Reborrow with a new lifetime /// /// Rust allows references like `&T` or `&mut T` to be "reborrowed" through /// coercion: essentially, the pointer is copied under a new, shorter, lifetime. @@ -101,19 +41,11 @@ impl<'a> DrawCtx<'a> { DrawMgr { h: self.h } } - /// Reborrow as a [`DrawCtx`] with a new lifetime - /// - /// Usually one uses [`Self::re`] to construct a [`DrawMgr`] to pass to a - /// child widget. This `re_ctx` may be useful within a single widget. - #[inline(always)] - pub fn re_ctx<'b>(&'b mut self) -> DrawCtx<'b> - where - 'a: 'b, - { - DrawCtx { - h: self.h, - id: self.id.clone(), - } + /// Construct from a [`DrawMgr`] and [`EventState`] + #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] + #[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] + pub fn new(h: &'a mut dyn DrawHandle) -> Self { + DrawMgr { h } } /// Access event-management state @@ -151,13 +83,9 @@ impl<'a> DrawCtx<'a> { /// /// Adds a new draw pass of type [`PassType::Clip`], with draw operations /// clipped to `rect` and translated by `offset. - pub fn with_clip_region(&mut self, rect: Rect, offset: Offset, mut f: F) { - // NOTE: using FnOnce in DrawHandle::new_pass would let us clone id outside the closure - let id = &self.id; - self.h.new_pass(rect, offset, PassType::Clip, &mut |h| { - let id = id.clone(); - f(DrawCtx { h, id }) - }); + pub fn with_clip_region(&mut self, rect: Rect, offset: Offset, mut f: F) { + self.h + .new_pass(rect, offset, PassType::Clip, &mut |h| f(DrawMgr { h })); } /// Draw to a new pass as an overlay (e.g. for pop-up menus) @@ -167,21 +95,19 @@ impl<'a> DrawCtx<'a> { /// /// The theme is permitted to enlarge the `rect` for the purpose of drawing /// a frame or shadow around this overlay, thus the - /// [`DrawCtx::get_clip_rect`] may be larger than expected. - pub fn with_overlay(&mut self, rect: Rect, mut f: F) { - let id = &self.id; + /// [`Self::get_clip_rect`] may be larger than expected. + pub fn with_overlay(&mut self, rect: Rect, mut f: F) { self.h .new_pass(rect, Offset::ZERO, PassType::Overlay, &mut |h| { - let id = id.clone(); - f(DrawCtx { h, id }) + f(DrawMgr { h }) }); } /// Target area for drawing /// /// Drawing is restricted to this [`Rect`], which may be the whole window, a - /// [clip region](DrawCtx::with_clip_region) or an - /// [overlay](DrawCtx::with_overlay). This may be used to cull hidden + /// [clip region](Self::with_clip_region) or an + /// [overlay](Self::with_overlay). This may be used to cull hidden /// items from lists inside a scrollable view. pub fn get_clip_rect(&self) -> Rect { self.h.get_clip_rect() @@ -192,13 +118,15 @@ impl<'a> DrawCtx<'a> { /// The frame dimensions are given by [`SizeMgr::frame`]. /// /// Note: for buttons, usage of [`Self::button`] does the same but allowing custom colours. - pub fn frame(&mut self, rect: Rect, style: FrameStyle) { - self.h.frame(&self.id, rect, style) + pub fn frame<'b>(&mut self, feature: impl Into>, style: FrameStyle) { + let f = feature.into(); + self.h.frame(f.0, f.1, style) } /// Draw a separator in the given `rect` - pub fn separator(&mut self, rect: Rect) { - self.h.separator(rect); + pub fn separator<'b>(&mut self, feature: impl Into>) { + let f = feature.into(); + self.h.separator(f.1); } /// Draw a selection box @@ -206,52 +134,70 @@ impl<'a> DrawCtx<'a> { /// This appears as a dashed box or similar around this `rect`. Note that /// the selection indicator is drawn *outside* of this rect, within a margin /// of size `inner_margin` that is expected to be present around this box. - pub fn selection_box(&mut self, rect: Rect) { - self.h.selection_box(rect); + pub fn selection_box<'b>(&mut self, feature: impl Into>) { + let f = feature.into(); + self.h.selection_box(f.1); } /// Draw text /// /// [`SizeMgr::text_bound`] should be called prior to this method to /// select a font, font size and wrap options (based on the [`TextClass`]). - pub fn text(&mut self, pos: Coord, text: &TextDisplay, class: TextClass) { - self.h.text(&self.id, pos, text, class); + pub fn text<'b>( + &mut self, + feature: impl Into>, + text: &TextDisplay, + class: TextClass, + ) { + let f = feature.into(); + self.h.text(f.0, f.1, text, class); } /// Draw text with effects /// - /// [`DrawCtx::text`] already supports *font* effects: bold, + /// [`Self::text`] already supports *font* effects: bold, /// emphasis, text size. In addition, this method supports underline and /// strikethrough effects. /// /// [`SizeMgr::text_bound`] should be called prior to this method to /// select a font, font size and wrap options (based on the [`TextClass`]). - pub fn text_effects(&mut self, pos: Coord, text: &dyn TextApi, class: TextClass) { - self.h.text_effects(&self.id, pos, text, class); + pub fn text_effects<'b>( + &mut self, + feature: impl Into>, + text: &dyn TextApi, + class: TextClass, + ) { + let f = feature.into(); + self.h.text_effects(f.0, f.1, text, class); } /// Draw an `AccelString` text /// - /// The `text` is drawn within the rect from `pos` to `text.env().bounds`. - /// /// [`SizeMgr::text_bound`] should be called prior to this method to /// select a font, font size and wrap options (based on the [`TextClass`]). - pub fn text_accel(&mut self, pos: Coord, text: &Text, class: TextClass) { - self.h.text_accel(&self.id, pos, text, class); + pub fn text_accel<'b>( + &mut self, + feature: impl Into>, + text: &Text, + class: TextClass, + ) { + let f = feature.into(); + self.h.text_accel(f.0, f.1, text, class); } /// Draw some text using the standard font, with a subset selected /// /// Other than visually highlighting the selection, this method behaves - /// identically to [`DrawCtx::text`]. It is likely to be replaced in the + /// identically to [`Self::text`]. It is likely to be replaced in the /// future by a higher-level API. - pub fn text_selected, R: RangeBounds>( + pub fn text_selected<'b, T: AsRef, R: RangeBounds>( &mut self, - pos: Coord, + feature: impl Into>, text: T, range: R, class: TextClass, ) { + let f = feature.into(); let start = match range.start_bound() { Bound::Included(n) => *n, Bound::Excluded(n) => *n + 1, @@ -264,23 +210,31 @@ impl<'a> DrawCtx<'a> { }; let range = Range { start, end }; self.h - .text_selected_range(&self.id, pos, text.as_ref(), range, class); + .text_selected_range(f.0, f.1, text.as_ref(), range, class); } /// Draw an edit marker at the given `byte` index on this `text` /// /// [`SizeMgr::text_bound`] should be called prior to this method to /// select a font, font size and wrap options (based on the [`TextClass`]). - pub fn text_cursor(&mut self, pos: Coord, text: &TextDisplay, class: TextClass, byte: usize) { - self.h.text_cursor(&self.id, pos, text, class, byte); + pub fn text_cursor<'b>( + &mut self, + feature: impl Into>, + text: &TextDisplay, + class: TextClass, + byte: usize, + ) { + let f = feature.into(); + self.h.text_cursor(f.0, f.1, text, class, byte); } /// Draw button sides, background and margin-area highlight /// /// Optionally, a specific colour may be used. // TODO: Allow theme-provided named colours? - pub fn button(&mut self, rect: Rect, col: Option) { - self.h.button(&self.id, rect, col); + pub fn button<'b>(&mut self, feature: impl Into>, col: Option) { + let f = feature.into(); + self.h.button(f.0, f.1, col); } /// Draw UI element: checkbox @@ -288,15 +242,17 @@ impl<'a> DrawCtx<'a> { /// The checkbox is a small, usually square, box with or without a check /// mark. A checkbox widget may include a text label, but that label is not /// part of this element. - pub fn checkbox(&mut self, rect: Rect, checked: bool) { - self.h.checkbox(&self.id, rect, checked); + pub fn checkbox<'b>(&mut self, feature: impl Into>, checked: bool) { + let f = feature.into(); + self.h.checkbox(f.0, f.1, checked); } /// Draw UI element: radiobox /// /// This is similar in appearance to a checkbox. - pub fn radiobox(&mut self, rect: Rect, checked: bool) { - self.h.radiobox(&self.id, rect, checked); + pub fn radiobox<'b>(&mut self, feature: impl Into>, checked: bool) { + let f = feature.into(); + self.h.radiobox(f.0, f.1, checked); } /// Draw UI element: scrollbar @@ -305,8 +261,15 @@ impl<'a> DrawCtx<'a> { /// - `rect`: area of whole widget (slider track) /// - `h_rect`: area of slider handle /// - `dir`: direction of bar - pub fn scrollbar(&mut self, id2: &WidgetId, rect: Rect, h_rect: Rect, dir: Direction) { - self.h.scrollbar(&self.id, id2, rect, h_rect, dir); + pub fn scrollbar<'b, 'c>( + &mut self, + feature: impl Into>, + handle: impl Into>, + dir: Direction, + ) { + let f = feature.into(); + let g = handle.into(); + self.h.scrollbar(f.0, g.0, f.1, g.1, dir); } /// Draw UI element: slider @@ -315,8 +278,15 @@ impl<'a> DrawCtx<'a> { /// - `rect`: area of whole widget (slider track) /// - `h_rect`: area of slider handle /// - `dir`: direction of slider (currently only LTR or TTB) - pub fn slider(&mut self, id2: &WidgetId, rect: Rect, h_rect: Rect, dir: Direction) { - self.h.slider(&self.id, id2, rect, h_rect, dir); + pub fn slider<'b, 'c>( + &mut self, + feature: impl Into>, + handle: impl Into>, + dir: Direction, + ) { + let f = feature.into(); + let g = handle.into(); + self.h.slider(f.0, g.0, f.1, g.1, dir); } /// Draw UI element: progress bar @@ -325,13 +295,15 @@ impl<'a> DrawCtx<'a> { /// - `dir`: direction of progress bar /// - `state`: highlighting information /// - `value`: progress value, between 0.0 and 1.0 - pub fn progress_bar(&mut self, rect: Rect, dir: Direction, value: f32) { - self.h.progress_bar(&self.id, rect, dir, value); + pub fn progress_bar<'b>(&mut self, feature: impl Into>, dir: Direction, value: f32) { + let f = feature.into(); + self.h.progress_bar(f.0, f.1, dir, value); } /// Draw an image - pub fn image(&mut self, id: ImageId, rect: Rect) { - self.h.image(id, rect); + pub fn image<'b>(&mut self, feature: impl Into>, id: ImageId) { + let f = feature.into(); + self.h.image(id, f.1); } } @@ -342,13 +314,6 @@ impl<'a> std::ops::BitOrAssign for DrawMgr<'a> { } } -impl<'a> std::ops::BitOrAssign for DrawCtx<'a> { - #[inline] - fn bitor_assign(&mut self, action: TkAction) { - self.h.components().2.send_action(action); - } -} - /// A handle to the active theme, used for drawing #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] #[cfg_attr(doc_cfg, doc(cfg(internal_doc)))] @@ -375,8 +340,8 @@ pub trait DrawHandle { /// Target area for drawing /// /// Drawing is restricted to this [`Rect`], which may be the whole window, a - /// [clip region](DrawCtx::with_clip_region) or an - /// [overlay](DrawCtx::with_overlay). This may be used to cull hidden + /// [clip region](Self::with_clip_region) or an + /// [overlay](Self::with_overlay). This may be used to cull hidden /// items from lists inside a scrollable view. fn get_clip_rect(&self) -> Rect; @@ -419,7 +384,7 @@ pub trait DrawHandle { /// select a font, font size and wrap options (based on the [`TextClass`]). fn text_accel(&mut self, id: &WidgetId, pos: Coord, text: &Text, class: TextClass); - /// Method used to implement [`DrawCtx::text_selected`] + /// Method used to implement [`Self::text_selected`] fn text_selected_range( &mut self, id: &WidgetId, @@ -600,10 +565,11 @@ mod test { // But we don't need to: we just want to test that methods are callable. let _scale = draw.size_mgr().scale_factor(); - let mut draw = draw.with_id(WidgetId::ROOT); + let id = WidgetId::ROOT; + let feature = IdCoord(&id, Coord::ZERO); let text = crate::text::Text::new_single("sample"); let class = TextClass::Label; - draw.text_selected(Coord::ZERO, &text, .., class) + draw.text_selected(feature, &text, .., class) } } diff --git a/crates/kas-core/src/theme/mod.rs b/crates/kas-core/src/theme/mod.rs index f8387a6dc..cdf255a43 100644 --- a/crates/kas-core/src/theme/mod.rs +++ b/crates/kas-core/src/theme/mod.rs @@ -9,13 +9,14 @@ mod draw; mod size; mod style; -pub use draw::{DrawCtx, DrawHandle, DrawMgr}; +pub use draw::{DrawHandle, DrawMgr}; pub use size::{SizeHandle, SizeMgr}; pub use style::*; #[allow(unused)] use crate::event::EventMgr; -use crate::TkAction; +use crate::geom::{Coord, Rect}; +use crate::{CoreData, TkAction, WidgetId}; use std::ops::{Deref, DerefMut}; /// Interface through which a theme can be adjusted at run-time @@ -60,3 +61,49 @@ impl ThemeControl for Box { self.deref_mut().set_theme(theme) } } + +/// Widget identifier and coordinate for a drawn feature +pub struct IdCoord<'a>(pub &'a WidgetId, pub Coord); + +impl<'a> From<&'a CoreData> for IdCoord<'a> { + fn from(core: &CoreData) -> IdCoord { + IdCoord(&core.id, core.rect.pos) + } +} + +impl<'a, W: crate::WidgetCore> From<&'a W> for IdCoord<'a> { + fn from(w: &W) -> IdCoord { + let core = w.core_data(); + IdCoord(&core.id, core.rect.pos) + } +} + +impl<'a, W: crate::WidgetCore> From<&'a mut W> for IdCoord<'a> { + fn from(w: &mut W) -> IdCoord { + let core = w.core_data(); + IdCoord(&core.id, core.rect.pos) + } +} + +/// Widget identifier and rect for a drawn feature +pub struct IdRect<'a>(pub &'a WidgetId, pub Rect); + +impl<'a> From<&'a CoreData> for IdRect<'a> { + fn from(core: &CoreData) -> IdRect { + IdRect(&core.id, core.rect) + } +} + +impl<'a, W: crate::WidgetCore> From<&'a W> for IdRect<'a> { + fn from(w: &W) -> IdRect { + let core = w.core_data(); + IdRect(&core.id, core.rect) + } +} + +impl<'a, W: crate::WidgetCore> From<&'a mut W> for IdRect<'a> { + fn from(w: &mut W) -> IdRect { + let core = w.core_data(); + IdRect(&core.id, core.rect) + } +} diff --git a/crates/kas-core/src/theme/size.rs b/crates/kas-core/src/theme/size.rs index fdde56ecf..d663aac81 100644 --- a/crates/kas-core/src/theme/size.rs +++ b/crates/kas-core/src/theme/size.rs @@ -8,7 +8,7 @@ use std::ops::Deref; #[allow(unused)] -use super::{DrawCtx, DrawMgr}; +use super::DrawMgr; use super::{FrameStyle, TextClass}; use crate::geom::Size; use crate::layout::{AxisInfo, FrameRules, Margins, SizeRules}; @@ -21,10 +21,10 @@ use crate::text::TextApiExt; /// /// This interface is provided to widgets in [`crate::Layout::size_rules`]. /// It may also be accessed through [`crate::event::EventMgr::size_mgr`], -/// [`DrawMgr::size_mgr`] and [`DrawCtx::size_mgr`]. +/// [`DrawMgr::size_mgr`]. /// /// Most methods get or calculate the size of some feature. These same features -/// may be drawn through [`DrawCtx`]. +/// may be drawn through [`DrawMgr`]. pub struct SizeMgr<'a>(&'a dyn SizeHandle); impl<'a> SizeMgr<'a> { @@ -150,12 +150,12 @@ impl<'a> SizeMgr<'a> { self.0.text_cursor_width() } - /// Size of the element drawn by [`DrawCtx::checkbox`]. + /// Size of the element drawn by [`DrawMgr::checkbox`]. pub fn checkbox(&self) -> Size { self.0.checkbox() } - /// Size of the element drawn by [`DrawCtx::radiobox`]. + /// Size of the element drawn by [`DrawMgr::radiobox`]. pub fn radiobox(&self) -> Size { self.0.radiobox() } @@ -255,10 +255,10 @@ pub trait SizeHandle { /// Width of an edit marker fn text_cursor_width(&self) -> f32; - /// Size of the element drawn by [`DrawCtx::checkbox`]. + /// Size of the element drawn by [`DrawMgr::checkbox`]. fn checkbox(&self) -> Size; - /// Size of the element drawn by [`DrawCtx::radiobox`]. + /// Size of the element drawn by [`DrawMgr::radiobox`]. fn radiobox(&self) -> Size; /// Dimensions for a scrollbar diff --git a/crates/kas-resvg/src/canvas.rs b/crates/kas-resvg/src/canvas.rs index 2e131fffb..620f043a1 100644 --- a/crates/kas-resvg/src/canvas.rs +++ b/crates/kas-resvg/src/canvas.rs @@ -138,7 +138,6 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id()); let (redraw, animate) = self.program.do_redraw_animate(); if redraw { draw.set_rect_mgr(|mgr| self.redraw(mgr)); @@ -147,7 +146,7 @@ widget! { draw.draw_device().animate(); } if let Some(id) = self.image_id { - draw.image(id, self.rect()); + draw.image(self, id); } } } diff --git a/crates/kas-resvg/src/svg.rs b/crates/kas-resvg/src/svg.rs index 6f38cde71..e3b288867 100644 --- a/crates/kas-resvg/src/svg.rs +++ b/crates/kas-resvg/src/svg.rs @@ -185,9 +185,8 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id()); if let Some(id) = self.image_id { - draw.image(id, self.rect()); + draw.image(self, id); } } } diff --git a/crates/kas-widgets/src/checkbox.rs b/crates/kas-widgets/src/checkbox.rs index 4c4f48da3..3eabc5e5e 100644 --- a/crates/kas-widgets/src/checkbox.rs +++ b/crates/kas-widgets/src/checkbox.rs @@ -41,7 +41,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - draw.with_id(self.id()).checkbox(self.core.rect, self.state); + draw.checkbox(&*self, self.state); } } diff --git a/crates/kas-widgets/src/combobox.rs b/crates/kas-widgets/src/combobox.rs index 994ebec15..02b0bafdb 100644 --- a/crates/kas-widgets/src/combobox.rs +++ b/crates/kas-widgets/src/combobox.rs @@ -48,9 +48,9 @@ widget! { None } - fn draw(&mut self, mut draw: DrawMgr) { - let draw = draw.with_id(self.id()); - self.layout().draw(draw); + fn draw(&mut self, draw: DrawMgr) { + let id = self.id(); + self.layout().draw(draw, &id); } } diff --git a/crates/kas-widgets/src/edit_field.rs b/crates/kas-widgets/src/edit_field.rs index df9a4ee97..14609c8ea 100644 --- a/crates/kas-widgets/src/edit_field.rs +++ b/crates/kas-widgets/src/edit_field.rs @@ -189,8 +189,8 @@ widget! { fn draw(&mut self, mut draw: DrawMgr) { // FIXME: let error = self.inner.has_error(); - draw.with_id(self.inner.id()).frame(self.core.rect, FrameStyle::EditBox); - self.inner.draw(draw.with_id(self.id()).re()); + draw.frame(&*self, FrameStyle::EditBox); + self.inner.draw(draw.re()); } } } @@ -413,16 +413,15 @@ widget! { } else { TextClass::Edit }; - let mut draw = draw.with_id(self.id()); draw.with_clip_region(self.rect(), self.view_offset, |mut draw| { if self.selection.is_empty() { - draw.text(self.rect().pos, self.text.as_ref(), class); + draw.text(&*self, self.text.as_ref(), class); } else { // TODO(opt): we could cache the selection rectangles here to make // drawing more efficient (self.text.highlight_lines(range) output). // The same applies to the edit marker below. draw.text_selected( - self.rect().pos, + &*self, &self.text, self.selection.range(), class, @@ -430,7 +429,7 @@ widget! { } if self.editable && draw.ev_state().has_char_focus(self.id_ref()).0 { draw.text_cursor( - self.rect().pos, + &*self, self.text.as_ref(), class, self.selection.edit_pos(), diff --git a/crates/kas-widgets/src/label.rs b/crates/kas-widgets/src/label.rs index ea30ba7c4..7775d23d2 100644 --- a/crates/kas-widgets/src/label.rs +++ b/crates/kas-widgets/src/label.rs @@ -37,13 +37,11 @@ widget! { #[cfg(feature = "min_spec")] default fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id()); - draw.text_effects(self.core.rect.pos, &self.label, TextClass::Label); + draw.text_effects(&*self, &self.label, TextClass::Label); } #[cfg(not(feature = "min_spec"))] fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id()); - draw.text_effects(self.core.rect.pos, &self.label, TextClass::Label); + draw.text_effects(&*self, &self.label, TextClass::Label); } } @@ -66,9 +64,8 @@ widget! { #[cfg(feature = "min_spec")] impl Layout for AccelLabel { fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id()); let accel = draw.ev_state().show_accel_labels(); - draw.text_accel(self.core.rect.pos, &self.label, accel, TextClass::Label); + draw.text_accel(&*self, &self.label, accel, TextClass::Label); } } @@ -76,15 +73,13 @@ impl Layout for AccelLabel { #[cfg(feature = "min_spec")] impl<'a> Layout for Label<&'a str> { fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id()); - draw.text(self.core.rect.pos, self.label.as_ref(), TextClass::Label); + draw.text(&*self, self.label.as_ref(), TextClass::Label); } } #[cfg(feature = "min_spec")] impl Layout for StringLabel { fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id()); - draw.text(self.core.rect.pos, self.label.as_ref(), TextClass::Label); + draw.text(&*self, self.label.as_ref(), TextClass::Label); } } diff --git a/crates/kas-widgets/src/menu/menu_entry.rs b/crates/kas-widgets/src/menu/menu_entry.rs index 12b44f9f1..befea73ea 100644 --- a/crates/kas-widgets/src/menu/menu_entry.rs +++ b/crates/kas-widgets/src/menu/menu_entry.rs @@ -40,10 +40,9 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id()); - draw.frame(self.core.rect, FrameStyle::MenuEntry); + draw.frame(&*self, FrameStyle::MenuEntry); draw.text_accel( - self.layout_label.pos, + kas::theme::IdCoord(self.id_ref(), self.layout_label.pos), &self.label, TextClass::MenuLabel, ); @@ -143,9 +142,9 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.checkbox.id()); - draw.frame(self.core.rect, FrameStyle::MenuEntry); - self.layout().draw(draw); + draw.frame(&*self, FrameStyle::MenuEntry); + let id = self.id(); + self.layout().draw(draw, &id); } } diff --git a/crates/kas-widgets/src/menu/submenu.rs b/crates/kas-widgets/src/menu/submenu.rs index 878226494..ec349b8db 100644 --- a/crates/kas-widgets/src/menu/submenu.rs +++ b/crates/kas-widgets/src/menu/submenu.rs @@ -143,10 +143,9 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id()); - draw.frame(self.core.rect, FrameStyle::MenuEntry); + draw.frame(&*self, FrameStyle::MenuEntry); draw.text_accel( - self.label_store.pos, + kas::theme::IdCoord(self.id_ref(), self.label_store.pos), &self.label, TextClass::MenuLabel, ); diff --git a/crates/kas-widgets/src/progress.rs b/crates/kas-widgets/src/progress.rs index e16b1f94a..b72df647e 100644 --- a/crates/kas-widgets/src/progress.rs +++ b/crates/kas-widgets/src/progress.rs @@ -100,9 +100,8 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id()); let dir = self.direction.as_direction(); - draw.progress_bar(self.core.rect, dir, self.value); + draw.progress_bar(&*self, dir, self.value); } } } diff --git a/crates/kas-widgets/src/radiobox.rs b/crates/kas-widgets/src/radiobox.rs index 95c70c18f..a888ea058 100644 --- a/crates/kas-widgets/src/radiobox.rs +++ b/crates/kas-widgets/src/radiobox.rs @@ -95,8 +95,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id()); - draw.radiobox(self.core.rect, self.state); + draw.radiobox(&*self, self.state); } } diff --git a/crates/kas-widgets/src/scroll.rs b/crates/kas-widgets/src/scroll.rs index c2fd233ad..7bec98bc6 100644 --- a/crates/kas-widgets/src/scroll.rs +++ b/crates/kas-widgets/src/scroll.rs @@ -131,7 +131,6 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id()); draw.with_clip_region(self.core.rect, self.scroll_offset(), |mut draw| { self.inner.draw(draw.re()) }); diff --git a/crates/kas-widgets/src/scroll_label.rs b/crates/kas-widgets/src/scroll_label.rs index 11c052572..9989a360c 100644 --- a/crates/kas-widgets/src/scroll_label.rs +++ b/crates/kas-widgets/src/scroll_label.rs @@ -54,17 +54,16 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id()); let class = TextClass::LabelScroll; draw.with_clip_region(self.rect(), self.view_offset, |mut draw| { if self.selection.is_empty() { - draw.text(self.rect().pos, self.text.as_ref(), class); + draw.text(&*self, self.text.as_ref(), class); } else { // TODO(opt): we could cache the selection rectangles here to make // drawing more efficient (self.text.highlight_lines(range) output). // The same applies to the edit marker below. draw.text_selected( - self.rect().pos, + &*self, &self.text, self.selection.range(), class, diff --git a/crates/kas-widgets/src/scrollbar.rs b/crates/kas-widgets/src/scrollbar.rs index c9d946c8f..7a43b6823 100644 --- a/crates/kas-widgets/src/scrollbar.rs +++ b/crates/kas-widgets/src/scrollbar.rs @@ -239,9 +239,8 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id()); let dir = self.direction.as_direction(); - draw.scrollbar(self.handle.id_ref(), self.core.rect, self.handle.rect(), dir); + draw.scrollbar(&*self, &self.handle, dir); } } @@ -515,7 +514,6 @@ widget! { } fn draw_(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id()); if self.show_bars.0 { self.horiz_bar.draw(draw.re()); } @@ -622,7 +620,6 @@ widget! { #[cfg(feature = "min_spec")] impl Layout for ScrollBars> { fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id()); // Enlarge clip region to *our* rect: draw.with_clip_region(self.core.rect, self.inner.scroll_offset(), |mut draw| { self.inner.inner_mut().draw(draw.re()) diff --git a/crates/kas-widgets/src/separator.rs b/crates/kas-widgets/src/separator.rs index f8f4a07c7..dd2f06d86 100644 --- a/crates/kas-widgets/src/separator.rs +++ b/crates/kas-widgets/src/separator.rs @@ -54,8 +54,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id()); - draw.separator(self.core.rect); + draw.separator(self); } } diff --git a/crates/kas-widgets/src/slider.rs b/crates/kas-widgets/src/slider.rs index ab42308a9..46816d42a 100644 --- a/crates/kas-widgets/src/slider.rs +++ b/crates/kas-widgets/src/slider.rs @@ -254,9 +254,8 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id()); let dir = self.direction.as_direction(); - draw.slider(self.handle.id_ref(), self.core.rect, self.handle.rect(), dir); + draw.slider(&*self, &self.handle, dir); } } diff --git a/crates/kas-widgets/src/splitter.rs b/crates/kas-widgets/src/splitter.rs index ed06eda4d..8af90db4e 100644 --- a/crates/kas-widgets/src/splitter.rs +++ b/crates/kas-widgets/src/splitter.rs @@ -195,7 +195,6 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id()); // as with find_id, there's not much harm in invoking the solver twice let solver = layout::RowPositionSolver::new(self.direction); @@ -205,7 +204,7 @@ widget! { let solver = layout::RowPositionSolver::new(self.direction); solver.for_children(&mut self.handles, draw.get_clip_rect(), |w| { - draw.separator(w.rect()) + draw.separator(w) }); } } diff --git a/crates/kas-widgets/src/sprite.rs b/crates/kas-widgets/src/sprite.rs index ff47dbc20..9b6e8dadc 100644 --- a/crates/kas-widgets/src/sprite.rs +++ b/crates/kas-widgets/src/sprite.rs @@ -49,9 +49,8 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id()); if let Some(id) = self.id { - draw.image(id, self.rect()); + draw.image(self, id); } } } diff --git a/crates/kas-widgets/src/stack.rs b/crates/kas-widgets/src/stack.rs index b743ec825..d20b110d2 100644 --- a/crates/kas-widgets/src/stack.rs +++ b/crates/kas-widgets/src/stack.rs @@ -79,7 +79,6 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id()); if self.active < self.widgets.len() { self.widgets[self.active].draw(draw.re()); } diff --git a/crates/kas-widgets/src/view/list_view.rs b/crates/kas-widgets/src/view/list_view.rs index 6dd8d5d6e..78371b727 100644 --- a/crates/kas-widgets/src/view/list_view.rs +++ b/crates/kas-widgets/src/view/list_view.rs @@ -592,14 +592,13 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id()); let offset = self.scroll_offset(); draw.with_clip_region(self.core.rect, offset, |mut draw| { for child in &mut self.widgets[..self.cur_len.cast()] { child.widget.draw(draw.re()); if let Some(ref key) = child.key { if self.selection.contains(key) { - draw.selection_box(child.widget.rect()); + draw.selection_box(&child.widget); } } } diff --git a/crates/kas-widgets/src/view/matrix_view.rs b/crates/kas-widgets/src/view/matrix_view.rs index 71741c7a8..2287b6e1e 100644 --- a/crates/kas-widgets/src/view/matrix_view.rs +++ b/crates/kas-widgets/src/view/matrix_view.rs @@ -586,7 +586,6 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id()); let offset = self.scroll_offset(); let rect = self.rect() + offset; let num = self.cur_len.cast(); @@ -594,12 +593,11 @@ widget! { for child in &mut self.widgets[..num] { // Note: we don't know which widgets within 0..num are // visible, so check intersection before drawing: - let child_rect = child.widget.rect(); - if rect.intersection(&child_rect).is_some() { + if rect.intersection(&child.widget.rect()).is_some() { if let Some(ref key) = child.key { child.widget.draw(draw.re()); if self.selection.contains(key) { - draw.selection_box(child_rect); + draw.selection_box(&child.widget); } } } diff --git a/crates/kas-widgets/src/window.rs b/crates/kas-widgets/src/window.rs index 7744f3442..9806c781d 100644 --- a/crates/kas-widgets/src/window.rs +++ b/crates/kas-widgets/src/window.rs @@ -50,7 +50,6 @@ widget! { #[inline] fn draw(&mut self, mut draw: DrawMgr) { - let mut draw = draw.with_id(self.id()); self.w.draw(draw.re()); for (_, popup) in &self.popups { if let Some(widget) = self.w.find_widget_mut(&popup.id) { From 7d6f8408e6859067a14ba9141f669cb29d98ada8 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 7 Mar 2022 11:28:04 +0000 Subject: [PATCH 13/16] Doc fixes --- crates/kas-core/src/core/widget.rs | 14 ++++++-------- crates/kas-core/src/theme/draw.rs | 8 +++++++- crates/kas-widgets/src/label.rs | 3 +-- crates/kas-widgets/src/view/driver.rs | 2 +- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/crates/kas-core/src/core/widget.rs b/crates/kas-core/src/core/widget.rs index 0c7680ce5..08a5cbd60 100644 --- a/crates/kas-core/src/core/widget.rs +++ b/crates/kas-core/src/core/widget.rs @@ -79,8 +79,8 @@ pub trait WidgetCore: Any + fmt::Debug { /// /// Dynamic widgets must implement this trait manually, since [`derive(Widget)`] /// cannot currently handle fields like `Vec`. Additionally, any -/// parent adding child widgets must ensure they get configured, either via -/// [`TkAction::RECONFIGURE`] or via [`SetRectMgr::configure`]. +/// parent adding child widgets must ensure they get configured by calling +/// [`SetRectMgr::configure`]. /// /// [`derive(Widget)`]: https://docs.rs/kas/latest/kas/macros/index.html#the-derivewidget-macro pub trait WidgetChildren: WidgetCore { @@ -150,9 +150,8 @@ pub trait WidgetChildren: WidgetCore { pub trait WidgetConfig: Layout { /// Pre-configure widget /// - /// Widgets are *configured* on window creation (before sizing) and when - /// [`TkAction::RECONFIGURE`] is sent. Child-widgets may alternatively be - /// configured locally by calling [`SetRectMgr::configure`]. + /// Widgets are *configured* on window creation (before sizing) or + /// dynamically via the parent calling [`SetRectMgr::configure`]. /// /// Configuration happens at least once /// before sizing and drawing, and may be repeated at a later time. @@ -169,9 +168,8 @@ pub trait WidgetConfig: Layout { /// Configure widget /// - /// Widgets are *configured* on window creation (before sizing) and when - /// [`TkAction::RECONFIGURE`] is sent. Child-widgets may alternatively be - /// configured locally by calling [`SetRectMgr::configure`]. + /// Widgets are *configured* on window creation (before sizing) or + /// dynamically via the parent calling [`SetRectMgr::configure`]. /// /// Configuration happens at least once /// before sizing and drawing, and may be repeated at a later time. diff --git a/crates/kas-core/src/theme/draw.rs b/crates/kas-core/src/theme/draw.rs index 3ce1e82f3..898cea830 100644 --- a/crates/kas-core/src/theme/draw.rs +++ b/crates/kas-core/src/theme/draw.rs @@ -22,7 +22,13 @@ use crate::{TkAction, WidgetId}; /// This interface is provided to widgets in [`crate::Layout::draw`]. /// Lower-level interfaces may be accessed through [`Self::draw_device`]. /// -/// Use [`DrawMgr::with_core`] to access draw methods. +/// Draw methods take a "feature"; any type convertible to [`IdRect`] or (for +/// text methods) [`IdCoord`]. For convenience where the target [`Rect`] or +/// [`Coord`] coincides with the widget's own `rect` (or `rect.pos`), this may +/// be constructed from `&CoreData` or `&W where W: WidgetCore`. For example: +/// +/// - `draw.checkbox(&*self, self.state);` — note `&*self` to convert from to +/// `&W` from `&mut W`, since the latter would cause borrow conflicts pub struct DrawMgr<'a> { h: &'a mut dyn DrawHandle, } diff --git a/crates/kas-widgets/src/label.rs b/crates/kas-widgets/src/label.rs index 7775d23d2..20de458d5 100644 --- a/crates/kas-widgets/src/label.rs +++ b/crates/kas-widgets/src/label.rs @@ -64,8 +64,7 @@ widget! { #[cfg(feature = "min_spec")] impl Layout for AccelLabel { fn draw(&mut self, mut draw: DrawMgr) { - let accel = draw.ev_state().show_accel_labels(); - draw.text_accel(&*self, &self.label, accel, TextClass::Label); + draw.text_accel(&*self, &self.label, TextClass::Label); } } diff --git a/crates/kas-widgets/src/view/driver.rs b/crates/kas-widgets/src/view/driver.rs index 8fec7ec0d..ee0b3a039 100644 --- a/crates/kas-widgets/src/view/driver.rs +++ b/crates/kas-widgets/src/view/driver.rs @@ -53,7 +53,7 @@ pub trait Driver: Debug + 'static { /// `update` method to update the model. /// /// Note that, additionally, when [`Response::Msg`] is returned, - /// [`kas::updatable::UpdatableHandler`] may be used to observe the message. + /// [`kas::updatable::Updatable`] may be used to observe the message. /// Often it will be sufficient to implement custom handling/update logic /// in only one of these places. fn get(&self, widget: &Self::Widget) -> Option; From c0ff40eb3d30065809099bef3437b898c8b8fb0c Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 8 Mar 2022 08:19:07 +0000 Subject: [PATCH 14/16] Add kas_core::theme::Background; fix edit error state --- crates/kas-core/src/layout/visitor.rs | 10 +++-- crates/kas-core/src/theme/draw.rs | 47 +++++++++++------------ crates/kas-core/src/theme/mod.rs | 2 +- crates/kas-theme/src/colors.rs | 27 +++++++++---- crates/kas-theme/src/flat_theme.rs | 40 ++++++++----------- crates/kas-theme/src/shaded_theme.rs | 44 ++++++++++----------- crates/kas-widgets/src/edit_field.rs | 10 +++-- crates/kas-widgets/src/menu/menu_entry.rs | 4 +- crates/kas-widgets/src/menu/submenu.rs | 2 +- 9 files changed, 98 insertions(+), 88 deletions(-) diff --git a/crates/kas-core/src/layout/visitor.rs b/crates/kas-core/src/layout/visitor.rs index 7acd7194f..099ac91a8 100644 --- a/crates/kas-core/src/layout/visitor.rs +++ b/crates/kas-core/src/layout/visitor.rs @@ -15,7 +15,7 @@ use crate::cast::Cast; use crate::draw::color::Rgb; use crate::geom::{Coord, Offset, Rect, Size}; use crate::text::{Align, TextApi, TextApiExt}; -use crate::theme::{DrawMgr, FrameStyle, IdCoord, IdRect, SizeMgr, TextClass}; +use crate::theme::{Background, DrawMgr, FrameStyle, IdCoord, IdRect, SizeMgr, TextClass}; use crate::WidgetId; use crate::{dir::Directional, WidgetConfig}; use std::any::Any; @@ -305,11 +305,15 @@ impl<'a> Layout<'a> { LayoutType::Single(child) | LayoutType::AlignSingle(child, _) => child.draw(draw.re()), LayoutType::AlignLayout(layout, _) => layout.draw_(draw, id), LayoutType::Frame(child, storage, style) => { - draw.frame(IdRect(id, storage.rect), *style); + draw.frame(IdRect(id, storage.rect), *style, Background::Default); child.draw_(draw, id); } LayoutType::Button(child, storage, color) => { - draw.button(IdRect(id, storage.rect), *color); + let bg = match color { + Some(rgb) => Background::Rgb(*rgb), + None => Background::Default, + }; + draw.frame(IdRect(id, storage.rect), FrameStyle::Button, bg); child.draw_(draw, id); } LayoutType::Visitor(layout) => layout.draw(draw, id), diff --git a/crates/kas-core/src/theme/draw.rs b/crates/kas-core/src/theme/draw.rs index 898cea830..0d7a69798 100644 --- a/crates/kas-core/src/theme/draw.rs +++ b/crates/kas-core/src/theme/draw.rs @@ -17,6 +17,23 @@ use crate::layout::SetRectMgr; use crate::text::{AccelString, Text, TextApi, TextDisplay}; use crate::{TkAction, WidgetId}; +/// Optional background colour +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Background { + /// Use theme/feature's default + Default, + /// Error state + Error, + /// A given color + Rgb(Rgb), +} + +impl Default for Background { + fn default() -> Self { + Background::Default + } +} + /// Draw interface /// /// This interface is provided to widgets in [`crate::Layout::draw`]. @@ -122,11 +139,9 @@ impl<'a> DrawMgr<'a> { /// Draw a frame inside the given `rect` /// /// The frame dimensions are given by [`SizeMgr::frame`]. - /// - /// Note: for buttons, usage of [`Self::button`] does the same but allowing custom colours. - pub fn frame<'b>(&mut self, feature: impl Into>, style: FrameStyle) { + pub fn frame<'b>(&mut self, feature: impl Into>, style: FrameStyle, bg: Background) { let f = feature.into(); - self.h.frame(f.0, f.1, style) + self.h.frame(f.0, f.1, style, bg) } /// Draw a separator in the given `rect` @@ -234,15 +249,6 @@ impl<'a> DrawMgr<'a> { self.h.text_cursor(f.0, f.1, text, class, byte); } - /// Draw button sides, background and margin-area highlight - /// - /// Optionally, a specific colour may be used. - // TODO: Allow theme-provided named colours? - pub fn button<'b>(&mut self, feature: impl Into>, col: Option) { - let f = feature.into(); - self.h.button(f.0, f.1, col); - } - /// Draw UI element: checkbox /// /// The checkbox is a small, usually square, box with or without a check @@ -354,7 +360,7 @@ pub trait DrawHandle { /// Draw a frame inside the given `rect` /// /// The frame dimensions are given by [`SizeHandle::frame`]. - fn frame(&mut self, id: &WidgetId, rect: Rect, style: FrameStyle); + fn frame(&mut self, id: &WidgetId, rect: Rect, style: FrameStyle, bg: Background); /// Draw a separator in the given `rect` fn separator(&mut self, rect: Rect); @@ -413,12 +419,6 @@ pub trait DrawHandle { byte: usize, ); - /// Draw button sides, background and margin-area highlight - /// - /// Optionally, a specific colour may be used. - // TODO: Allow theme-provided named colours? - fn button(&mut self, id: &WidgetId, rect: Rect, col: Option); - /// Draw UI element: checkbox /// /// The checkbox is a small, usually square, box with or without a check @@ -489,8 +489,8 @@ macro_rules! impl_ { fn get_clip_rect(&self) -> Rect { self.deref().get_clip_rect() } - fn frame(&mut self, id: &WidgetId, rect: Rect, style: FrameStyle) { - self.deref_mut().frame(id, rect, style); + fn frame(&mut self, id: &WidgetId, rect: Rect, style: FrameStyle, bg: Background) { + self.deref_mut().frame(id, rect, style, bg); } fn separator(&mut self, rect: Rect) { self.deref_mut().separator(rect); @@ -530,9 +530,6 @@ macro_rules! impl_ { fn text_cursor(&mut self, id: &WidgetId, pos: Coord, text: &TextDisplay, class: TextClass, byte: usize) { self.deref_mut().text_cursor(id, pos, text, class, byte) } - fn button(&mut self, id: &WidgetId, rect: Rect, col: Option) { - self.deref_mut().button(id, rect, col) - } fn checkbox(&mut self, id: &WidgetId, rect: Rect, checked: bool) { self.deref_mut().checkbox(id, rect, checked) } diff --git a/crates/kas-core/src/theme/mod.rs b/crates/kas-core/src/theme/mod.rs index cdf255a43..6f0f450f4 100644 --- a/crates/kas-core/src/theme/mod.rs +++ b/crates/kas-core/src/theme/mod.rs @@ -9,7 +9,7 @@ mod draw; mod size; mod style; -pub use draw::{DrawHandle, DrawMgr}; +pub use draw::{Background, DrawHandle, DrawMgr}; pub use size::{SizeHandle, SizeMgr}; pub use style::*; diff --git a/crates/kas-theme/src/colors.rs b/crates/kas-theme/src/colors.rs index 8c1b9c949..1db72e908 100644 --- a/crates/kas-theme/src/colors.rs +++ b/crates/kas-theme/src/colors.rs @@ -7,6 +7,7 @@ use kas::draw::color::{Rgba, Rgba8Srgb}; use kas::event::EventState; +use kas::theme::Background; use kas::WidgetId; use std::str::FromStr; @@ -295,14 +296,26 @@ impl ColorsLinear { } } + /// Extract from [`Background`] + pub fn from_bg(&self, bg: Background, state: InputState, force_accent: bool) -> Rgba { + let use_accent = force_accent || state.depress() || state.nav_focus(); + let col = match bg { + _ if state.disabled() => self.edit_bg_disabled, + Background::Default if use_accent => self.accent_soft, + Background::Default => self.background, + Background::Error => self.edit_bg_error, + Background::Rgb(rgb) => rgb.into(), + }; + Self::adjust_for_state(col, state) + } + /// Get colour of a text area, depending on state - pub fn edit_bg(&self, state: InputState, state_error: bool) -> Rgba { - let mut col = if state.disabled() { - self.edit_bg_disabled - } else if state_error { - self.edit_bg_error - } else { - self.edit_bg + pub fn from_edit_bg(&self, bg: Background, state: InputState) -> Rgba { + let mut col = match bg { + _ if state.disabled() => self.edit_bg_disabled, + Background::Default => self.edit_bg, + Background::Error => self.edit_bg_error, + Background::Rgb(rgb) => rgb.into(), }; if state.depress() { col = col.multiply(MULT_DEPRESS); diff --git a/crates/kas-theme/src/flat_theme.rs b/crates/kas-theme/src/flat_theme.rs index 3f9fe8f65..81cef722f 100644 --- a/crates/kas-theme/src/flat_theme.rs +++ b/crates/kas-theme/src/flat_theme.rs @@ -21,7 +21,7 @@ use kas::geom::*; use kas::text::format::FormattableText; use kas::text::{fonts, AccelString, Effect, Text, TextApi, TextDisplay}; use kas::theme::{self, SizeHandle, ThemeControl}; -use kas::theme::{FrameStyle, TextClass}; +use kas::theme::{Background, FrameStyle, TextClass}; use kas::{TkAction, WidgetId}; // Used to ensure a rectangular background is inside a circular corner. @@ -288,9 +288,9 @@ where inner } - fn edit_box(&mut self, id: &WidgetId, outer: Quad, state_error: bool) { + fn edit_box(&mut self, id: &WidgetId, outer: Quad, bg: Background) { let state = InputState::new_except_depress(self.ev, id); - let col_bg = self.cols.edit_bg(state, state_error); + let col_bg = self.cols.from_edit_bg(bg, state); if col_bg != self.cols.background { let inner = outer.shrink(self.w.dims.button_frame as f32 * BG_SHRINK_FACTOR); self.draw.rect(inner, col_bg); @@ -375,7 +375,7 @@ where self.draw.get_clip_rect() } - fn frame(&mut self, id: &WidgetId, rect: Rect, style: FrameStyle) { + fn frame(&mut self, id: &WidgetId, rect: Rect, style: FrameStyle, bg: Background) { let outer = Quad::conv(rect); match style { FrameStyle::InnerMargin => (), @@ -412,8 +412,15 @@ where self.draw.rounded_frame(outer, inner, 0.0, col); } } - FrameStyle::Button => self.button(id, rect, None), - FrameStyle::EditBox => self.edit_box(id, outer, false), + FrameStyle::Button => { + let state = InputState::new_all(self.ev, id); + let outer = Quad::conv(rect); + + let col_bg = self.cols.from_bg(bg, state, false); + let col_frame = self.cols.nav_region(state).unwrap_or(self.cols.frame); + self.button_frame(outer, col_frame, col_bg, state); + } + FrameStyle::EditBox => self.edit_box(id, outer, bg), } } @@ -548,20 +555,6 @@ where } } - fn button(&mut self, id: &WidgetId, rect: Rect, col: Option) { - let state = InputState::new_all(self.ev, id); - let outer = Quad::conv(rect); - - let col_bg = if state.depress() || state.nav_focus() && !state.disabled() { - self.cols.accent_soft - } else { - col.map(|c| c.into()).unwrap_or(self.cols.background) - }; - let col_bg = ColorsLinear::adjust_for_state(col_bg, state); - let col_frame = self.cols.nav_region(state).unwrap_or(self.cols.frame); - self.button_frame(outer, col_frame, col_bg, state); - } - fn checkbox(&mut self, id: &WidgetId, rect: Rect, checked: bool) { let anim_fade = self.w.anim.fade_bool_1m(self.draw.draw, id, checked); @@ -569,7 +562,8 @@ where let outer = Quad::conv(rect); let col_frame = self.cols.nav_region(state).unwrap_or(self.cols.frame); - let inner = self.button_frame(outer, col_frame, self.cols.edit_bg(state, false), state); + let col_bg = self.cols.from_edit_bg(Default::default(), state); + let inner = self.button_frame(outer, col_frame, col_bg, state); if anim_fade < 1.0 { let inner = inner.shrink((2 * self.w.dims.inner_margin) as f32); @@ -602,8 +596,8 @@ where self.draw.circle_2col(shadow_outer, col1, col2); } - self.draw - .circle(outer, 0.0, self.cols.edit_bg(state, false)); + let col_bg = self.cols.from_edit_bg(Default::default(), state); + self.draw.circle(outer, 0.0, col_bg); const F: f32 = 2.0 * (1.0 - BG_SHRINK_FACTOR); // match checkbox frame let r = 1.0 - F * self.w.dims.button_frame as f32 / rect.size.0 as f32; diff --git a/crates/kas-theme/src/shaded_theme.rs b/crates/kas-theme/src/shaded_theme.rs index f9e885c64..d75d4f177 100644 --- a/crates/kas-theme/src/shaded_theme.rs +++ b/crates/kas-theme/src/shaded_theme.rs @@ -16,7 +16,7 @@ use kas::draw::{color::Rgba, *}; use kas::event::EventState; use kas::geom::*; use kas::text::{AccelString, Text, TextApi, TextDisplay}; -use kas::theme::{self, SizeHandle, ThemeControl}; +use kas::theme::{self, Background, SizeHandle, ThemeControl}; use kas::theme::{FrameStyle, TextClass}; use kas::{TkAction, WidgetId}; @@ -286,7 +286,7 @@ where self.draw.get_clip_rect() } - fn frame(&mut self, id: &WidgetId, rect: Rect, style: FrameStyle) { + fn frame(&mut self, id: &WidgetId, rect: Rect, style: FrameStyle, bg: Background) { match style { FrameStyle::Frame => { let outer = Quad::conv(rect); @@ -306,13 +306,27 @@ where self.draw.rect(outer, col); } } - FrameStyle::Button => self.button(id, rect, None), + FrameStyle::Button => { + let state = InputState::new_all(self.ev, id); + let outer = Quad::conv(rect); + let inner = outer.shrink(self.w.dims.button_frame as f32); + let col_bg = self.cols.from_bg(bg, state, true); + + self.draw + .shaded_round_frame(outer, inner, (0.0, 0.6), col_bg); + self.draw.rect(inner, col_bg); + + if let Some(col) = self.cols.nav_region(state) { + let outer = outer.shrink(self.w.dims.inner_margin as f32); + self.draw.rounded_frame(outer, inner, 0.6, col); + } + } FrameStyle::EditBox => { let state = InputState::new_except_depress(self.ev, id); - let bg_col = self.cols.edit_bg(state, false); + let bg_col = self.cols.from_edit_bg(bg, state); self.draw_edit_box(rect, bg_col, self.cols.nav_region(state)); } - style => self.as_flat().frame(id, rect, style), + style => self.as_flat().frame(id, rect, style, bg), } } @@ -369,27 +383,11 @@ where self.as_flat().text_cursor(id, pos, text, class, byte); } - fn button(&mut self, id: &WidgetId, rect: Rect, col: Option) { - let state = InputState::new_all(self.ev, id); - let outer = Quad::conv(rect); - let inner = outer.shrink(self.w.dims.button_frame as f32); - let col = col.map(|c| c.into()).unwrap_or(self.cols.accent_soft); - let col = ColorsLinear::adjust_for_state(col, state); - - self.draw.shaded_round_frame(outer, inner, (0.0, 0.6), col); - self.draw.rect(inner, col); - - if let Some(col) = self.cols.nav_region(state) { - let outer = outer.shrink(self.w.dims.inner_margin as f32); - self.draw.rounded_frame(outer, inner, 0.6, col); - } - } - fn checkbox(&mut self, id: &WidgetId, rect: Rect, checked: bool) { let state = InputState::new_all(self.ev, id); let anim_fade = self.w.anim.fade_bool_1m(self.draw.draw, id, checked); - let bg_col = self.cols.edit_bg(state, false); + let bg_col = self.cols.from_edit_bg(Default::default(), state); let nav_col = self.cols.nav_region(state).or(Some(bg_col)); let inner = self.draw_edit_box(rect, bg_col, nav_col); @@ -406,7 +404,7 @@ where let state = InputState::new_all(self.ev, id); let anim_fade = self.w.anim.fade_bool_1m(self.draw.draw, id, checked); - let bg_col = self.cols.edit_bg(state, false); + let bg_col = self.cols.from_edit_bg(Default::default(), state); let nav_col = self.cols.nav_region(state).or(Some(bg_col)); let inner = self.draw_edit_box(rect, bg_col, nav_col); diff --git a/crates/kas-widgets/src/edit_field.rs b/crates/kas-widgets/src/edit_field.rs index 14609c8ea..c83fea378 100644 --- a/crates/kas-widgets/src/edit_field.rs +++ b/crates/kas-widgets/src/edit_field.rs @@ -12,7 +12,7 @@ use kas::geom::Vec2; use kas::layout::{self, FrameStorage}; use kas::prelude::*; use kas::text::SelectionHelper; -use kas::theme::{FrameStyle, TextClass}; +use kas::theme::{Background, FrameStyle, TextClass}; use std::fmt::Debug; use std::ops::Range; use unicode_segmentation::{GraphemeCursor, UnicodeSegmentation}; @@ -188,8 +188,12 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - // FIXME: let error = self.inner.has_error(); - draw.frame(&*self, FrameStyle::EditBox); + let bg = if self.inner.has_error() { + Background::Error + } else { + Background::Default + }; + draw.frame(&*self, FrameStyle::EditBox, bg); self.inner.draw(draw.re()); } } diff --git a/crates/kas-widgets/src/menu/menu_entry.rs b/crates/kas-widgets/src/menu/menu_entry.rs index befea73ea..8df32dd7d 100644 --- a/crates/kas-widgets/src/menu/menu_entry.rs +++ b/crates/kas-widgets/src/menu/menu_entry.rs @@ -40,7 +40,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - draw.frame(&*self, FrameStyle::MenuEntry); + draw.frame(&*self, FrameStyle::MenuEntry, Default::default()); draw.text_accel( kas::theme::IdCoord(self.id_ref(), self.layout_label.pos), &self.label, @@ -142,7 +142,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - draw.frame(&*self, FrameStyle::MenuEntry); + draw.frame(&*self, FrameStyle::MenuEntry, Default::default()); let id = self.id(); self.layout().draw(draw, &id); } diff --git a/crates/kas-widgets/src/menu/submenu.rs b/crates/kas-widgets/src/menu/submenu.rs index ec349b8db..f3745c87d 100644 --- a/crates/kas-widgets/src/menu/submenu.rs +++ b/crates/kas-widgets/src/menu/submenu.rs @@ -143,7 +143,7 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { - draw.frame(&*self, FrameStyle::MenuEntry); + draw.frame(&*self, FrameStyle::MenuEntry, Default::default()); draw.text_accel( kas::theme::IdCoord(self.id_ref(), self.label_store.pos), &self.label, From 3bd88351dc9c62d95095537f2644b3afbd65a2bf Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 8 Mar 2022 08:22:29 +0000 Subject: [PATCH 15/16] Fixes for feature gat --- crates/kas-theme/src/flat_theme.rs | 2 +- crates/kas-theme/src/multi.rs | 2 +- crates/kas-theme/src/shaded_theme.rs | 2 +- crates/kas-theme/src/theme_dst.rs | 4 ++-- crates/kas-theme/src/traits.rs | 4 ++-- crates/kas-wgpu/src/window.rs | 2 +- examples/custom-theme.rs | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/kas-theme/src/flat_theme.rs b/crates/kas-theme/src/flat_theme.rs index 81cef722f..028c1e3a7 100644 --- a/crates/kas-theme/src/flat_theme.rs +++ b/crates/kas-theme/src/flat_theme.rs @@ -207,7 +207,7 @@ where fn draw_handle<'a>( &'a self, draw: DrawIface<'a, DS>, - ev: &mut EventState, + ev: &'a mut EventState, w: &'a mut Self::Window, ) -> Self::DrawHandle<'a> { w.anim.update(); diff --git a/crates/kas-theme/src/multi.rs b/crates/kas-theme/src/multi.rs index 1f9a6907c..d83302481 100644 --- a/crates/kas-theme/src/multi.rs +++ b/crates/kas-theme/src/multi.rs @@ -172,7 +172,7 @@ impl Theme for MultiTheme { fn draw_handle<'a>( &'a self, draw: DrawIface<'a, DS>, - ev: &mut EventState, + ev: &'a mut EventState, window: &'a mut Self::Window, ) -> StackDst { self.themes[self.active].draw_handle(draw, ev, window) diff --git a/crates/kas-theme/src/shaded_theme.rs b/crates/kas-theme/src/shaded_theme.rs index d75d4f177..a803911ed 100644 --- a/crates/kas-theme/src/shaded_theme.rs +++ b/crates/kas-theme/src/shaded_theme.rs @@ -147,7 +147,7 @@ where fn draw_handle<'a>( &'a self, draw: DrawIface<'a, DS>, - ev: &mut EventState, + ev: &'a mut EventState, w: &'a mut Self::Window, ) -> Self::DrawHandle<'a> { w.anim.update(); diff --git a/crates/kas-theme/src/theme_dst.rs b/crates/kas-theme/src/theme_dst.rs index 1e6a67cc8..743c2e8f1 100644 --- a/crates/kas-theme/src/theme_dst.rs +++ b/crates/kas-theme/src/theme_dst.rs @@ -89,7 +89,7 @@ pub trait ThemeDst: ThemeControl { fn draw_handle<'a>( &'a self, draw: DrawIface<'a, DS>, - ev: &mut EventState, + ev: &'a mut EventState, window: &'a mut dyn Window, ) -> StackDst; @@ -196,7 +196,7 @@ impl<'a, DS: DrawSharedImpl, T: Theme> ThemeDst for T { fn draw_handle<'b>( &'b self, draw: DrawIface<'b, DS>, - ev: &mut EventState, + ev: &'b mut EventState, window: &'b mut dyn Window, ) -> StackDst { let window = window.as_any_mut().downcast_mut().unwrap(); diff --git a/crates/kas-theme/src/traits.rs b/crates/kas-theme/src/traits.rs index 3f1718d3d..28fdabdf5 100644 --- a/crates/kas-theme/src/traits.rs +++ b/crates/kas-theme/src/traits.rs @@ -120,7 +120,7 @@ pub trait Theme: ThemeControl { fn draw_handle<'a>( &'a self, draw: DrawIface<'a, DS>, - ev: &mut EventState, + ev: &'a mut EventState, window: &'a mut Self::Window, ) -> Self::DrawHandle<'a>; @@ -184,7 +184,7 @@ impl, DS: DrawSharedImpl> Theme for Box { fn draw_handle<'a>( &'a self, draw: DrawIface<'a, DS>, - ev: &mut EventState, + ev: &'a mut EventState, window: &'a mut Self::Window, ) -> Self::DrawHandle<'a> { self.deref().draw_handle(draw, ev, window) diff --git a/crates/kas-wgpu/src/window.rs b/crates/kas-wgpu/src/window.rs index fb6a1a61b..b983f6bdc 100644 --- a/crates/kas-wgpu/src/window.rs +++ b/crates/kas-wgpu/src/window.rs @@ -365,7 +365,7 @@ impl>> Window { shared .theme .draw_handle(draw, &mut self.ev_state, &mut self.theme_window); - let draw_mgr = DrawMgr::new(&mut draw_handle, false); + let draw_mgr = DrawMgr::new(&mut draw_handle); self.widget.draw(draw_mgr); } } diff --git a/examples/custom-theme.rs b/examples/custom-theme.rs index 06002f6cd..4ec4c71f2 100644 --- a/examples/custom-theme.rs +++ b/examples/custom-theme.rs @@ -74,7 +74,7 @@ where fn draw_handle<'a>( &'a self, draw: DrawIface<'a, DS>, - ev: &mut EvState, + ev: &'a mut EventState, window: &'a mut Self::Window, ) -> Self::DrawHandle<'a> { Theme::::draw_handle(&self.inner, draw, ev, window) From 04f8f487b503a173aa24d45265197b8a666ea498 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 8 Mar 2022 08:43:55 +0000 Subject: [PATCH 16/16] Fix for Rustc 1.56 --- crates/kas-core/src/core/widget_id.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/kas-core/src/core/widget_id.rs b/crates/kas-core/src/core/widget_id.rs index 358238dc8..b4f6d810e 100644 --- a/crates/kas-core/src/core/widget_id.rs +++ b/crates/kas-core/src/core/widget_id.rs @@ -744,7 +744,7 @@ mod test { #[test] fn test_parent() { fn test(seq: &[usize]) { - println!("seq: {seq:?}"); + println!("seq: {:?}", seq); let mut id = WidgetId::ROOT; let len = seq.len(); for key in &seq[..len - 1] {