From 63c78eaf5f672562a980fbe819edd1c71756cef7 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 27 Apr 2020 18:30:58 +0100 Subject: [PATCH 01/11] Doc fixes --- kas-theme/src/col.rs | 2 +- kas-wgpu/src/draw/custom.rs | 4 ++-- src/event/events.rs | 2 +- src/event/handler.rs | 4 ++-- src/event/manager/mgr_pub.rs | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/kas-theme/src/col.rs b/kas-theme/src/col.rs index 60a0f2a45..738fe4a8e 100644 --- a/kas-theme/src/col.rs +++ b/kas-theme/src/col.rs @@ -119,7 +119,7 @@ impl ThemeColours { } } - /// Get colour of a [text] area, depending on state + /// Get colour of a text area, depending on state pub fn bg_col(&self, state: InputState) -> Colour { if state.disabled { self.bg_disabled diff --git a/kas-wgpu/src/draw/custom.rs b/kas-wgpu/src/draw/custom.rs index ba79deb71..c17e65e73 100644 --- a/kas-wgpu/src/draw/custom.rs +++ b/kas-wgpu/src/draw/custom.rs @@ -127,8 +127,8 @@ pub trait CustomPipe { /// Per-window state for a custom draw pipe /// /// One instance is constructed per window. Since the [`CustomPipe`] is not -/// accessible during a widget's [`Layout::draw`] calls, this struct must batch -/// per-frame draw data. +/// accessible during a widget's [`kas::Layout::draw`] calls, this struct must +/// batch per-frame draw data. pub trait CustomWindow { /// User parameter type type Param; diff --git a/src/event/events.rs b/src/event/events.rs index 38f554da4..4fb2d893b 100644 --- a/src/event/events.rs +++ b/src/event/events.rs @@ -6,7 +6,7 @@ //! Event handling: events #[allow(unused)] -use super::Manager; // for doc-links +use super::{Manager, Response}; // for doc-links use super::{MouseButton, UpdateHandle, VirtualKeyCode}; use crate::geom::{Coord, DVec2}; diff --git a/src/event/handler.rs b/src/event/handler.rs index 380c39f7e..02792cac5 100644 --- a/src/event/handler.rs +++ b/src/event/handler.rs @@ -68,7 +68,7 @@ pub trait SendEvent: Handler { /// disabled widgets should not forward any events. /// /// The following logic is recommended for routing events: - /// ```norun + /// ```no_test /// if self.is_disabled() { /// return Response::Unhandled(event); /// } @@ -83,7 +83,7 @@ pub trait SendEvent: Handler { /// } /// ``` /// Parents which don't handle any events themselves may simplify this: - /// ```norun + /// ```no_test /// if !self.is_disabled() && id <= self.w.id() { /// return self.w.send(mgr, id, event); /// } diff --git a/src/event/manager/mgr_pub.rs b/src/event/manager/mgr_pub.rs index a07b56c64..89c1f6018 100644 --- a/src/event/manager/mgr_pub.rs +++ b/src/event/manager/mgr_pub.rs @@ -245,8 +245,8 @@ impl<'a> Manager<'a> { /// [`Event::Activate`].) /// /// Only one widget can be a fallback, and the *first* to set itself wins. - /// This is primarily used to allow [`ScrollRegion`] to respond to - /// navigation keys when no widget has focus. + /// This is primarily used to allow [`kas::widget::ScrollRegion`] to + /// respond to navigation keys when no widget has focus. pub fn register_nav_fallback(&mut self, id: WidgetId) { if self.mgr.nav_fallback.is_none() { debug!("Manager: nav_fallback = {}", id); From de9e7bd0100fc6e53510e59b18e1685a7ca55577 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 28 Apr 2020 18:18:49 +0100 Subject: [PATCH 02/11] Require Widget: Any and Widget::Msg: 'static The Any bound requires the latter bound. Note that without this bound, using a Msg with 'a could be possible but would require parametrising Msg over 'a; the latter requires GATs (unstable). Probably this would be more complex than useful. --- src/data.rs | 2 +- src/traits.rs | 3 ++- src/traits/impls.rs | 14 +++++++------- src/widget/button.rs | 12 ++++++------ src/widget/checkbox.rs | 24 ++++++++++++------------ src/widget/combobox.rs | 2 +- src/widget/menu/menu_entry.rs | 20 ++++++++++---------- src/widget/radiobox.rs | 24 ++++++++++++------------ src/widget/separator.rs | 2 +- src/widget/slider.rs | 4 +++- 10 files changed, 55 insertions(+), 52 deletions(-) diff --git a/src/data.rs b/src/data.rs index b329030ce..b2d7706b7 100644 --- a/src/data.rs +++ b/src/data.rs @@ -192,7 +192,7 @@ impl CompleteAlignment { /// /// Using a generic `` over [`Direction`] allows compile-time /// substitution via the [`Right`], [`Down`], [`Left`] and [`Up`] instantiations. -pub trait Directional: Copy + Sized + std::fmt::Debug { +pub trait Directional: Copy + Sized + std::fmt::Debug + 'static { /// Direction flipped over diagonal (i.e. Down ↔ Right) type Flipped: Directional; diff --git a/src/traits.rs b/src/traits.rs index a79371a78..666e58eef 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -5,6 +5,7 @@ //! Widget traits +use std::any::Any; use std::fmt; use std::ops::DerefMut; @@ -36,7 +37,7 @@ impl CloneTo for T { /// /// This trait is almost always implemented via the /// [`derive(Widget)` macro](macros/index.html#the-derivewidget-macro). -pub trait WidgetCore: fmt::Debug { +pub trait WidgetCore: Any + fmt::Debug { /// Get direct access to the [`CoreData`] providing property storage. fn core_data(&self) -> &CoreData; diff --git a/src/traits/impls.rs b/src/traits/impls.rs index ab14437f8..9d003bbcf 100644 --- a/src/traits/impls.rs +++ b/src/traits/impls.rs @@ -12,7 +12,7 @@ use crate::geom::{Coord, Rect}; use crate::layout::{AxisInfo, SizeRules}; use crate::{AlignHints, CoreData, WidgetId}; -impl WidgetCore for Box> { +impl WidgetCore for Box> { fn core_data(&self) -> &CoreData { self.as_ref().core_data() } @@ -32,7 +32,7 @@ impl WidgetCore for Box> { } } -impl WidgetChildren for Box> { +impl WidgetChildren for Box> { fn len(&self) -> usize { self.as_ref().len() } @@ -58,7 +58,7 @@ impl WidgetChildren for Box> { } } -impl WidgetConfig for Box> { +impl WidgetConfig for Box> { fn configure(&mut self, mgr: &mut Manager) { self.as_mut().configure(mgr); } @@ -71,7 +71,7 @@ impl WidgetConfig for Box> { } } -impl Layout for Box> { +impl Layout for Box> { fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { self.as_mut().size_rules(size_handle, axis) } @@ -89,7 +89,7 @@ impl Layout for Box> { } } -impl event::Handler for Box> { +impl event::Handler for Box> { type Msg = M; fn activation_via_press(&self) -> bool { @@ -101,13 +101,13 @@ impl event::Handler for Box> { } } -impl event::SendEvent for Box> { +impl event::SendEvent for Box> { fn send(&mut self, mgr: &mut Manager, id: WidgetId, event: Event) -> Response { self.as_mut().send(mgr, id, event) } } -impl Widget for Box> {} +impl Widget for Box> {} impl Clone for Box> { fn clone(&self) -> Self { diff --git a/src/widget/button.rs b/src/widget/button.rs index 56e14824c..b6d3bd5dd 100644 --- a/src/widget/button.rs +++ b/src/widget/button.rs @@ -18,7 +18,7 @@ use kas::prelude::*; #[handler(handle=noauto)] #[widget(config=noauto)] #[derive(Clone, Debug, Default, Widget)] -pub struct TextButton { +pub struct TextButton { #[widget_core] core: kas::CoreData, keys: SmallVec<[VirtualKeyCode; 4]>, @@ -27,7 +27,7 @@ pub struct TextButton { msg: M, } -impl WidgetConfig for TextButton { +impl WidgetConfig for TextButton { fn configure(&mut self, mgr: &mut Manager) { for key in &self.keys { mgr.add_accel_key(*key, self.id()); @@ -39,7 +39,7 @@ impl WidgetConfig for TextButton { } } -impl Layout for TextButton { +impl Layout for TextButton { fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { let sides = size_handle.button_surround(); let margins = size_handle.outer_margins(); @@ -65,7 +65,7 @@ impl Layout for TextButton { } } -impl TextButton { +impl TextButton { /// Construct a button with a given `label` and `msg` /// /// The message `msg` is returned to the parent widget on activation. Any @@ -99,7 +99,7 @@ impl TextButton { } } -impl HasText for TextButton { +impl HasText for TextButton { fn get_text(&self) -> &str { &self.label } @@ -110,7 +110,7 @@ impl HasText for TextButton { } } -impl event::Handler for TextButton { +impl event::Handler for TextButton { type Msg = M; #[inline] diff --git a/src/widget/checkbox.rs b/src/widget/checkbox.rs index 68abcd1e3..4b60d442c 100644 --- a/src/widget/checkbox.rs +++ b/src/widget/checkbox.rs @@ -19,14 +19,14 @@ use kas::prelude::*; #[widget(config(key_nav = true))] #[handler(handle=noauto)] #[derive(Clone, Default, Widget)] -pub struct CheckBoxBare { +pub struct CheckBoxBare { #[widget_core] core: CoreData, state: bool, on_toggle: Option M>>, } -impl Debug for CheckBoxBare { +impl Debug for CheckBoxBare { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, @@ -36,7 +36,7 @@ impl Debug for CheckBoxBare { } } -impl Layout for CheckBoxBare { +impl Layout for CheckBoxBare { fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { let size = size_handle.checkbox(); self.core.rect.size = size; @@ -56,7 +56,7 @@ impl Layout for CheckBoxBare { } } -impl CheckBoxBare { +impl CheckBoxBare { /// Construct a checkbox which calls `f` when toggled /// /// This is a shortcut for `CheckBoxBare::new().on_toggle(f)`. @@ -101,7 +101,7 @@ impl CheckBoxBare { } } -impl CheckBoxBare { +impl CheckBoxBare { /// Set the initial state of the checkbox. #[inline] pub fn state(mut self, state: bool) -> Self { @@ -110,7 +110,7 @@ impl CheckBoxBare { } } -impl HasBool for CheckBoxBare { +impl HasBool for CheckBoxBare { fn get_bool(&self) -> bool { self.state } @@ -121,7 +121,7 @@ impl HasBool for CheckBoxBare { } } -impl event::Handler for CheckBoxBare { +impl event::Handler for CheckBoxBare { type Msg = M; #[inline] @@ -150,7 +150,7 @@ impl event::Handler for CheckBoxBare { #[layout(row, area=checkbox)] #[handler(msg = M, generics = <> where M: From)] #[derive(Clone, Default, Widget)] -pub struct CheckBox { +pub struct CheckBox { #[widget_core] core: CoreData, #[layout_data] @@ -161,7 +161,7 @@ pub struct CheckBox { label: Label, } -impl Debug for CheckBox { +impl Debug for CheckBox { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, @@ -171,7 +171,7 @@ impl Debug for CheckBox { } } -impl CheckBox { +impl CheckBox { /// Construct a checkbox with a given `label` which calls `f` when toggled. /// /// This is a shortcut for `CheckBox::new(label).on_toggle(f)`. @@ -228,7 +228,7 @@ impl CheckBox { } } -impl CheckBox { +impl CheckBox { /// Set the initial state of the checkbox. #[inline] pub fn state(mut self, state: bool) -> Self { @@ -237,7 +237,7 @@ impl CheckBox { } } -impl HasBool for CheckBox { +impl HasBool for CheckBox { #[inline] fn get_bool(&self) -> bool { self.checkbox.get_bool() diff --git a/src/widget/combobox.rs b/src/widget/combobox.rs index 1a1653027..3e81ca314 100644 --- a/src/widget/combobox.rs +++ b/src/widget/combobox.rs @@ -70,7 +70,7 @@ impl kas::Layout for ComboBox { } } -impl ComboBox { +impl ComboBox { /// Construct a combobox /// /// A combobox presents a menu with a fixed set of choices when clicked. diff --git a/src/widget/menu/menu_entry.rs b/src/widget/menu/menu_entry.rs index 980cb4933..490b5c834 100644 --- a/src/widget/menu/menu_entry.rs +++ b/src/widget/menu/menu_entry.rs @@ -18,7 +18,7 @@ use kas::widget::{CheckBoxBare, Label}; #[widget(config(key_nav = true))] #[handler(handle=noauto)] #[derive(Clone, Debug, Default, Widget)] -pub struct MenuEntry { +pub struct MenuEntry { #[widget_core] core: kas::CoreData, label: CowString, @@ -26,7 +26,7 @@ pub struct MenuEntry { msg: M, } -impl Layout for MenuEntry { +impl Layout for MenuEntry { fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { let size = size_handle.menu_frame(); self.label_off = size.into(); @@ -46,7 +46,7 @@ impl Layout for MenuEntry { } } -impl MenuEntry { +impl MenuEntry { /// Construct a menu item with a given `label` and `msg` /// /// The message `msg` is emitted on activation. Any @@ -67,7 +67,7 @@ impl MenuEntry { } } -impl HasText for MenuEntry { +impl HasText for MenuEntry { fn get_text(&self) -> &str { &self.label } @@ -78,7 +78,7 @@ impl HasText for MenuEntry { } } -impl event::Handler for MenuEntry { +impl event::Handler for MenuEntry { type Msg = M; fn handle(&mut self, _: &mut Manager, event: Event) -> Response { @@ -92,7 +92,7 @@ impl event::Handler for MenuEntry { /// A menu entry which can be toggled #[handler(msg = M, generics = <> where M: From)] #[derive(Clone, Default, Widget)] -pub struct MenuToggle { +pub struct MenuToggle { #[widget_core] core: CoreData, layout_data: layout::FixedRowStorage<[SizeRules; 3], [u32; 2]>, @@ -102,7 +102,7 @@ pub struct MenuToggle { label: Label, } -impl Debug for MenuToggle { +impl Debug for MenuToggle { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, @@ -112,7 +112,7 @@ impl Debug for MenuToggle { } } -impl MenuToggle { +impl MenuToggle { /// Construct a togglable menu entry with a given `label` and closure /// /// This is a shortcut for `MenuToggle::new(label).on_toggle(f)`. @@ -169,7 +169,7 @@ impl MenuToggle { } } -impl kas::Layout for MenuToggle { +impl kas::Layout for MenuToggle { // NOTE: This code is mostly copied from the macro expansion. // Only draw() is significantly different. fn size_rules( @@ -220,7 +220,7 @@ impl kas::Layout for MenuToggle { self.label.draw(draw_handle, mgr, state.disabled); } } -impl HasBool for MenuToggle { +impl HasBool for MenuToggle { #[inline] fn get_bool(&self) -> bool { self.checkbox.get_bool() diff --git a/src/widget/radiobox.rs b/src/widget/radiobox.rs index d7180481e..52763de24 100644 --- a/src/widget/radiobox.rs +++ b/src/widget/radiobox.rs @@ -20,7 +20,7 @@ use kas::prelude::*; #[handler(handle=noauto)] #[widget(config=noauto)] #[derive(Clone, Widget)] -pub struct RadioBoxBare { +pub struct RadioBoxBare { #[widget_core] core: CoreData, state: bool, @@ -28,7 +28,7 @@ pub struct RadioBoxBare { on_activate: Option M>>, } -impl Debug for RadioBoxBare { +impl Debug for RadioBoxBare { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, @@ -38,7 +38,7 @@ impl Debug for RadioBoxBare { } } -impl WidgetConfig for RadioBoxBare { +impl WidgetConfig for RadioBoxBare { fn configure(&mut self, mgr: &mut Manager) { mgr.update_on_handle(self.handle, self.id()); } @@ -48,7 +48,7 @@ impl WidgetConfig for RadioBoxBare { } } -impl event::Handler for RadioBoxBare { +impl event::Handler for RadioBoxBare { type Msg = M; #[inline] @@ -85,7 +85,7 @@ impl event::Handler for RadioBoxBare { } } -impl Layout for RadioBoxBare { +impl Layout for RadioBoxBare { fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { let size = size_handle.radiobox(); self.core.rect.size = size; @@ -138,7 +138,7 @@ impl RadioBoxBare { } } -impl RadioBoxBare { +impl RadioBoxBare { /// Construct a radiobox which calls `f` when toggled /// /// This is a shortcut for `RadioBoxBare::new().on_activate(f)`. @@ -169,7 +169,7 @@ impl RadioBoxBare { } } -impl HasBool for RadioBoxBare { +impl HasBool for RadioBoxBare { fn get_bool(&self) -> bool { self.state } @@ -184,7 +184,7 @@ impl HasBool for RadioBoxBare { #[layout(row, area=radiobox)] #[handler(msg = M, generics = <> where M: From)] #[derive(Clone, Widget)] -pub struct RadioBox { +pub struct RadioBox { #[widget_core] core: CoreData, #[layout_data] @@ -195,7 +195,7 @@ pub struct RadioBox { label: Label, } -impl Debug for RadioBox { +impl Debug for RadioBox { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, @@ -205,7 +205,7 @@ impl Debug for RadioBox { } } -impl RadioBox { +impl RadioBox { /// Construct a radiobox with a given `label` which calls `f` when toggled. /// /// This is a shortcut for `RadioBox::new(label).on_activate(f)`. @@ -265,7 +265,7 @@ impl RadioBox { } } -impl RadioBox { +impl RadioBox { /// Set the initial state of the radiobox. #[inline] pub fn state(mut self, state: bool) -> Self { @@ -274,7 +274,7 @@ impl RadioBox { } } -impl HasBool for RadioBox { +impl HasBool for RadioBox { #[inline] fn get_bool(&self) -> bool { self.radiobox.get_bool() diff --git a/src/widget/separator.rs b/src/widget/separator.rs index 65fdcb429..14d5d6cbe 100644 --- a/src/widget/separator.rs +++ b/src/widget/separator.rs @@ -18,7 +18,7 @@ use kas::prelude::*; /// if no other widget will fill spare space. #[handler(msg=M)] #[derive(Clone, Debug, Default, Widget)] -pub struct Separator { +pub struct Separator { #[widget_core] core: CoreData, _msg: PhantomData, diff --git a/src/widget/slider.rs b/src/widget/slider.rs index 4fa12059b..81e1e32f4 100644 --- a/src/widget/slider.rs +++ b/src/widget/slider.rs @@ -23,6 +23,7 @@ pub trait SliderType: + Sub + ApproxInto + ApproxFrom + + 'static { } @@ -33,7 +34,8 @@ impl< + Add + Sub + ApproxInto - + ApproxFrom, + + ApproxFrom + + 'static, > SliderType for T { } From d18dba19826454479bcac55ea22bc2999903857f Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 28 Apr 2020 18:29:43 +0100 Subject: [PATCH 03/11] Support is, downcast_ref and downcast_mut on all widgets --- kas-macros/src/lib.rs | 3 +++ src/traits.rs | 26 ++++++++++++++++++++++++++ src/traits/impls.rs | 7 +++++++ 3 files changed, 36 insertions(+) diff --git a/kas-macros/src/lib.rs b/kas-macros/src/lib.rs index 405ab618f..316e7dff4 100644 --- a/kas-macros/src/lib.rs +++ b/kas-macros/src/lib.rs @@ -108,6 +108,9 @@ pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { impl #impl_generics kas::WidgetCore for #name #ty_generics #where_clause { + fn as_any(&self) -> &dyn std::any::Any { self } + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self } + fn core_data(&self) -> &kas::CoreData { &self.#core_data } diff --git a/src/traits.rs b/src/traits.rs index 666e58eef..e9ec5f737 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -33,11 +33,37 @@ impl CloneTo for T { } } +impl dyn WidgetCore { + /// Forwards to the method defined on the type `Any`. + #[inline] + pub fn is(&self) -> bool { + Any::is::(self.as_any()) + } + + /// Forwards to the method defined on the type `Any`. + #[inline] + pub fn downcast_ref(&self) -> Option<&T> { + Any::downcast_ref::(self.as_any()) + } + + /// Forwards to the method defined on the type `Any`. + #[inline] + pub fn downcast_mut(&mut self) -> Option<&mut T> { + Any::downcast_mut::(self.as_any_mut()) + } +} + /// Base widget functionality /// /// This trait is almost always implemented via the /// [`derive(Widget)` macro](macros/index.html#the-derivewidget-macro). pub trait WidgetCore: Any + fmt::Debug { + /// Get self as type `Any` + fn as_any(&self) -> &dyn Any; + + /// Get self as type `Any` (mutable) + fn as_any_mut(&mut self) -> &mut dyn Any; + /// Get direct access to the [`CoreData`] providing property storage. fn core_data(&self) -> &CoreData; diff --git a/src/traits/impls.rs b/src/traits/impls.rs index 9d003bbcf..201ac1e7e 100644 --- a/src/traits/impls.rs +++ b/src/traits/impls.rs @@ -13,6 +13,13 @@ use crate::layout::{AxisInfo, SizeRules}; use crate::{AlignHints, CoreData, WidgetId}; impl WidgetCore for Box> { + fn as_any(&self) -> &dyn Any { + self.as_ref().as_any() + } + fn as_any_mut(&mut self) -> &mut dyn Any { + self.as_mut().as_any_mut() + } + fn core_data(&self) -> &CoreData { self.as_ref().core_data() } From 5af33ba3185a2ca64d9959ee771701223a442c0e Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 29 Apr 2020 11:15:41 +0100 Subject: [PATCH 04/11] Add Boxed trait --- kas-wgpu/examples/gallery.rs | 4 ++-- src/prelude.rs | 2 +- src/traits.rs | 26 +++++++------------------- src/traits/utils.rs | 31 +++++++++++++++++++++++++++++++ 4 files changed, 41 insertions(+), 22 deletions(-) create mode 100644 src/traits/utils.rs diff --git a/kas-wgpu/examples/gallery.rs b/kas-wgpu/examples/gallery.rs index 1dd0ede5c..428f5cc06 100644 --- a/kas-wgpu/examples/gallery.rs +++ b/kas-wgpu/examples/gallery.rs @@ -11,9 +11,9 @@ use kas::class::HasText; use kas::event::{Manager, Response, UpdateHandle, VoidMsg, VoidResponse}; -use kas::macros::{make_widget, VoidMsg}; +use kas::prelude::*; use kas::widget::*; -use kas::{Right, TkAction, Widget, WidgetId}; +use kas::Right; #[derive(Clone, Debug, VoidMsg)] enum Item { diff --git a/src/prelude.rs b/src/prelude.rs index 68e13b710..bd6fe240b 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -17,7 +17,7 @@ pub use kas::geom::{Coord, Rect, Size}; pub use kas::macros::*; pub use kas::{class, draw, event, geom, layout, widget}; pub use kas::{Align, AlignHints, Direction, Directional, WidgetId}; +pub use kas::{Boxed, TkAction, TkWindow}; pub use kas::{CloneTo, Layout, ThemeApi, Widget, WidgetChildren, WidgetConfig, WidgetCore}; pub use kas::{CoreData, LayoutData}; pub use kas::{CowString, CowStringL}; -pub use kas::{TkAction, TkWindow}; diff --git a/src/traits.rs b/src/traits.rs index e9ec5f737..74e40c4a6 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -18,20 +18,9 @@ use crate::TkAction; // for doc links use crate::{AlignHints, CoreData, Direction, WidgetId, WindowId}; mod impls; +mod utils; -/// Support trait for cloning boxed unsized objects -#[cfg_attr(not(feature = "internal_doc"), doc(hidden))] -pub trait CloneTo { - unsafe fn clone_to(&self, out: *mut Self); -} - -impl CloneTo for T { - unsafe fn clone_to(&self, out: *mut Self) { - let x = self.clone(); - std::ptr::copy(&x, out, 1); - std::mem::forget(x); - } -} +pub use utils::*; impl dyn WidgetCore { /// Forwards to the method defined on the type `Any`. @@ -397,12 +386,11 @@ pub trait Layout: WidgetChildren { /// `fn foo(w: &mut dyn Widget)`, or, e.g. /// `fn foo(w: &mut dyn WidgetConfig)` (note that `WidgetConfig` is the last unparameterised /// trait in the widget trait family). -pub trait Widget: event::SendEvent { - /// Return a boxed version of the widget - fn boxed(self) -> Box> - where - Self: Sized + 'static, - { +pub trait Widget: event::SendEvent {} + +impl Boxed for W { + type T = dyn Widget; + fn boxed(self) -> Box { Box::new(self) } } diff --git a/src/traits/utils.rs b/src/traits/utils.rs new file mode 100644 index 000000000..bd0417140 --- /dev/null +++ b/src/traits/utils.rs @@ -0,0 +1,31 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License in the LICENSE-APACHE file or at: +// https://www.apache.org/licenses/LICENSE-2.0 + +//! Utility traits +//! +//! Arguably this stuff belongs in a different crate (even libstd). + +/// Support trait for cloning boxed unsized objects +#[cfg_attr(not(feature = "internal_doc"), doc(hidden))] +pub trait CloneTo { + unsafe fn clone_to(&self, out: *mut Self); +} + +impl CloneTo for T { + unsafe fn clone_to(&self, out: *mut Self) { + let x = self.clone(); + std::ptr::copy(&x, out, 1); + std::mem::forget(x); + } +} + +/// Provides a convenient `.boxed()` method on implementors +pub trait Boxed { + /// Target type (`dyn Trait`) + type T: ?Sized; + + /// Boxing method + fn boxed(self) -> Box; +} From cd35d0a3d1c624613a78ab304ae79527af0c54ac Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 29 Apr 2020 11:32:19 +0100 Subject: [PATCH 05/11] Move widget traits to new module --- src/traits.rs | 385 +----------------------------------------- src/traits/widget.rs | 389 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 394 insertions(+), 380 deletions(-) create mode 100644 src/traits/widget.rs diff --git a/src/traits.rs b/src/traits.rs index 74e40c4a6..9f3c550cc 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -9,391 +9,16 @@ use std::any::Any; use std::fmt; use std::ops::DerefMut; -use crate::draw::{DrawHandle, InputState, SizeHandle}; -use crate::event::{self, Manager, ManagerState}; -use crate::geom::{Coord, Rect}; -use crate::layout::{self, AxisInfo, SizeRules}; -#[allow(unused)] -use crate::TkAction; // for doc links -use crate::{AlignHints, CoreData, Direction, WidgetId, WindowId}; +use crate::draw::SizeHandle; +use crate::event::{self, Manager}; +use crate::{layout, Direction, WidgetId, WindowId}; mod impls; mod utils; +mod widget; pub use utils::*; - -impl dyn WidgetCore { - /// Forwards to the method defined on the type `Any`. - #[inline] - pub fn is(&self) -> bool { - Any::is::(self.as_any()) - } - - /// Forwards to the method defined on the type `Any`. - #[inline] - pub fn downcast_ref(&self) -> Option<&T> { - Any::downcast_ref::(self.as_any()) - } - - /// Forwards to the method defined on the type `Any`. - #[inline] - pub fn downcast_mut(&mut self) -> Option<&mut T> { - Any::downcast_mut::(self.as_any_mut()) - } -} - -/// Base widget functionality -/// -/// This trait is almost always implemented via the -/// [`derive(Widget)` macro](macros/index.html#the-derivewidget-macro). -pub trait WidgetCore: Any + fmt::Debug { - /// Get self as type `Any` - fn as_any(&self) -> &dyn Any; - - /// Get self as type `Any` (mutable) - fn as_any_mut(&mut self) -> &mut dyn Any; - - /// Get direct access to the [`CoreData`] providing property storage. - fn core_data(&self) -> &CoreData; - - /// Get mutable access to the [`CoreData`] providing property storage. - /// - /// This should not normally be needed by user code. - #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] - fn core_data_mut(&mut self) -> &mut CoreData; - - /// Get the widget's numeric identifier - #[inline] - fn id(&self) -> WidgetId { - self.core_data().id - } - - /// Get whether the widget is disabled - #[inline] - fn is_disabled(&self) -> bool { - self.core_data().disabled - } - - /// Get 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 { - self.core_data().rect - } - - /// Get the name of the widget struct - fn widget_name(&self) -> &'static str; - - /// Erase type - fn as_widget(&self) -> &dyn WidgetConfig; - /// Erase type - fn as_widget_mut(&mut self) -> &mut dyn WidgetConfig; - - /// Construct [`InputState`] - /// - /// The `disabled` flag is inherited from parents. [`InputState::disabled`] - /// will be true if either `disabled` or `self.is_disabled()` are true. - /// - /// The error state defaults to `false` since most widgets don't support - /// this. - fn input_state(&self, mgr: &ManagerState, disabled: bool) -> InputState { - let id = self.core_data().id; - InputState { - disabled: self.core_data().disabled || disabled, - error: false, - hover: mgr.is_hovered(id), - depress: mgr.is_depressed(id), - nav_focus: mgr.nav_focus(id), - char_focus: mgr.char_focus(id), - } - } -} - -/// Listing of a widget's children -/// -/// Usually this is implemented by `derive(Widget)`, but for dynamic widgets it -/// may have to be implemented manually. Note that if the results of these -/// methods ever change, one must send [`TkAction::Reconfigure`]. -/// TODO: full reconfigure may be too slow; find a better option. -pub trait WidgetChildren: WidgetCore { - /// Get the number of child widgets - fn len(&self) -> usize; - - /// Get a reference to a child widget by index, or `None` if the index is - /// out of bounds. - /// - /// For convenience, `Index` is implemented via this method. - /// - /// Required: `index < self.len()`. - fn get(&self, index: usize) -> Option<&dyn WidgetConfig>; - - /// Mutable variant of get - /// - /// Warning: directly adjusting a widget without requiring reconfigure or - /// redraw may break the UI. If a widget is replaced, a reconfigure **must** - /// be requested. This can be done via [`Manager::send_action`]. - /// This method may be removed in the future. - fn get_mut(&mut self, index: usize) -> Option<&mut dyn WidgetConfig>; - - /// Check whether `id` is a descendant - /// - /// This function assumes that `id` is a valid widget. - #[inline] - fn is_ancestor_of(&self, id: WidgetId) -> bool { - self.find(id).is_some() - } - - /// Find a child widget by identifier - /// - /// This requires that the widget tree has already been configured by - /// [`event::ManagerState::configure`]. - /// - /// If the widget is disabled, this returns `None` without recursing children. - fn find(&self, id: WidgetId) -> Option<&dyn WidgetConfig> { - if id == self.id() { - return Some(self.as_widget()); - } else if id > self.id() { - return None; - } - - for i in 0..self.len() { - if let Some(w) = self.get(i) { - if id > w.id() { - continue; - } - return w.find(id); - } - break; - } - None - } - - /// Find a child widget by identifier - /// - /// This requires that the widget tree has already been configured by - /// [`ManagerState::configure`]. - fn find_mut(&mut self, id: WidgetId) -> Option<&mut dyn WidgetConfig> { - if id == self.id() { - return Some(self.as_widget_mut()); - } else if id > self.id() { - return None; - } - - for i in 0..self.len() { - if self.get(i).map(|w| id > w.id()).unwrap_or(true) { - continue; - } - if let Some(w) = self.get_mut(i) { - return w.find_mut(id); - } - break; - } - None - } - - /// Walk through all widgets, calling `f` once on each. - /// - /// This walk is iterative (nonconcurrent), depth-first, and always calls - /// `f` on self *after* walking through all children. - fn walk(&self, f: &mut dyn FnMut(&dyn WidgetConfig)) { - for i in 0..self.len() { - if let Some(w) = self.get(i) { - w.walk(f); - } - } - f(self.as_widget()); - } - - /// Walk through all widgets, calling `f` once on each. - /// - /// This walk is iterative (nonconcurrent), depth-first, and always calls - /// `f` on self *after* walking through all children. - fn walk_mut(&mut self, f: &mut dyn FnMut(&mut dyn WidgetConfig)) { - for i in 0..self.len() { - if let Some(w) = self.get_mut(i) { - w.walk_mut(f); - } - } - f(self.as_widget_mut()); - } -} - -/// Widget configuration -/// -/// This trait allows some configuration of widget behaviour. All items have -/// default values. This trait may be implemented by hand, or may be derived -/// with the [`derive(Widget)` macro](macros/index.html#the-derivewidget-macro) -/// by use of a `#[widget_config]` attribute. Optionally, this attribute can -/// contain parameters, e.g. `#[widget_config(key_nav = true)]`. -// TODO(specialization): provide a blanket implementation, so that users only -// need implement manually when they have something to configure. -pub trait WidgetConfig: Layout { - /// Configure widget - /// - /// Widgets are *configured* on window creation and when - /// [`kas::TkAction::Reconfigure`] is sent. - /// - /// Configure is called before resizing (but after calculation of the - /// initial window size). This method is called after - /// a [`WidgetId`] has been assigned to self, and after `configure` has - /// been called on each child. - /// - /// The default implementation of this method does nothing. - fn configure(&mut self, _: &mut Manager) {} - - /// Is this widget navigable via Tab key? - /// - /// Defaults to `false`. - fn key_nav(&self) -> bool { - false - } - - /// Which cursor icon should be used on hover? - /// - /// Defaults to [`event::CursorIcon::Default`]. - fn cursor_icon(&self) -> event::CursorIcon { - event::CursorIcon::Default - } -} - -/// Positioning and drawing routines for widgets -/// -/// This trait contains methods concerned with positioning of contents -/// as well as low-level event handling. -/// -/// For a description of the widget size model, see [`SizeRules`]. -pub trait Layout: WidgetChildren { - /// Get size rules for the given axis. - /// - /// This method takes `&mut self` to allow local caching of child widget - /// configuration for future `size_rules` and `set_rect` calls. - /// - /// Optionally, this method may set `self.rect().size` to the widget's ideal - /// size for use by [`Layout::set_rect`] when setting alignment. - /// - /// If operating on one axis and the other is fixed, then the `other` - /// parameter is used for the fixed dimension. Additionally, one may assume - /// that `size_rules` has previously been called on the fixed axis with the - /// current widget configuration. - fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules; - - /// Adjust to the given size. - /// - /// For widgets with children, this is usually implemented via the derive - /// [macro](kas::macros). For non-parent widgets which stretch to fill - /// available space, the default implementation suffices. For non-parent - /// widgets which react to alignment, this is a little more complex to - /// implement, and can be done in one of two ways: - /// - /// 1. Shrinking to ideal area and aligning within available space (e.g. - /// `CheckBoxBare` widget) - /// 2. Filling available space and applying alignment to contents (e.g. - /// `Label` widget) - /// - /// One may assume that `size_rules` has been called for each axis with the - /// current widget configuration. - #[inline] - fn set_rect(&mut self, rect: Rect, _align: AlignHints) { - self.core_data_mut().rect = rect; - } - - /// Get translation of a child - /// - /// Children may live in a translated coordinate space relative to their - /// parent. This method returns an offset which should be *added* to a - /// coordinate to translate *into* the child's coordinate space or - /// subtracted to translate out. - /// - /// In most cases, the translation will be zero. Widgets should return - /// [`Coord::ZERO`] for non-existant children. - #[inline] - fn translation(&self, _child_index: usize) -> Coord { - Coord::ZERO - } - - /// Iterate through children in spatial order - /// - /// Returns a "range" of children, by index, in spatial order. Unlike - /// `std::ops::Range` this is inclusive and reversible, e.g. `(1, 3)` means - /// `1, 2, 3` and `(5, 2)` means `5, 4, 3, 2`. As a special case, - /// `(_, std::usize::MAX)` means the range is empty. - /// - /// Disabled widgets should return an empty range, otherwise they should - /// return a range over children in spatial order (left-to-right then - /// top-to-bottom). Widgets outside the parent's rect (i.e. popups) should - /// be excluded. - /// - /// The default implementation returns - /// `(0, WidgetChildren::len(self).wrapping_sub(1))` when not disabled - /// which should suffice for most widgets. - fn spatial_range(&self) -> (usize, usize) { - (0, WidgetChildren::len(self).wrapping_sub(1)) - } - - /// Find a widget by coordinate - /// - /// Returns the identifier of the widget containing this `coord`, if any. - /// Should only return `None` when `coord` is outside the widget's rect, - /// but this is not guaranteed. - /// - /// Implementations should: - /// - /// 1. return `None` if `!self.rect().contains(coord)` - /// 2. if, for any child (containing `coord`), `child.find_id(coord)` - /// returns `Some(id)`, return that - /// 3. otherwise, return `Some(self.id())` - /// - /// Exceptionally, a widget may deviate from this behaviour, but only when - /// the coord is within the widget's rect (example: `CheckBox` contains an - /// embedded `CheckBoxBare` and always forwards this child's id). - /// - /// This must not be called before [`Layout::set_rect`]. - #[inline] - fn find_id(&self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } - Some(self.id()) - } - - /// Draw a widget - /// - /// This method is called to draw each visible widget (and should not - /// attempt recursion on child widgets). - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool); -} - -/// Widget trait -/// -/// This is one of a family of widget traits, all of which must be implemented -/// for a functional widget. In general, most traits will be implemented via the -/// [`derive(Widget)` macro](macros/index.html#the-derivewidget-macro). -/// -/// A [`Widget`] may be passed into a generic function via -/// `fn foo(w: &mut W)` or via -/// `fn foo(w: &mut dyn Widget)`, or, e.g. -/// `fn foo(w: &mut dyn WidgetConfig)` (note that `WidgetConfig` is the last unparameterised -/// trait in the widget trait family). -pub trait Widget: event::SendEvent {} - -impl Boxed for W { - type T = dyn Widget; - fn boxed(self) -> Box { - Box::new(self) - } -} +pub use widget::*; /// Trait to describe the type needed by the layout implementation. /// diff --git a/src/traits/widget.rs b/src/traits/widget.rs new file mode 100644 index 000000000..2a1635b2f --- /dev/null +++ b/src/traits/widget.rs @@ -0,0 +1,389 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License in the LICENSE-APACHE file or at: +// https://www.apache.org/licenses/LICENSE-2.0 + +//! Widget traits + +use std::any::Any; +use std::fmt; + +use super::Boxed; +use crate::draw::{DrawHandle, InputState, SizeHandle}; +use crate::event::{self, Manager, ManagerState}; +use crate::geom::{Coord, Rect}; +use crate::layout::{AxisInfo, SizeRules}; +use crate::{AlignHints, CoreData, TkAction, WidgetId}; + +impl dyn WidgetCore { + /// Forwards to the method defined on the type `Any`. + #[inline] + pub fn is(&self) -> bool { + Any::is::(self.as_any()) + } + + /// Forwards to the method defined on the type `Any`. + #[inline] + pub fn downcast_ref(&self) -> Option<&T> { + Any::downcast_ref::(self.as_any()) + } + + /// Forwards to the method defined on the type `Any`. + #[inline] + pub fn downcast_mut(&mut self) -> Option<&mut T> { + Any::downcast_mut::(self.as_any_mut()) + } +} + +/// Base widget functionality +/// +/// This trait is almost always implemented via the +/// [`derive(Widget)` macro](macros/index.html#the-derivewidget-macro). +pub trait WidgetCore: Any + fmt::Debug { + /// Get self as type `Any` + fn as_any(&self) -> &dyn Any; + + /// Get self as type `Any` (mutable) + fn as_any_mut(&mut self) -> &mut dyn Any; + + /// Get direct access to the [`CoreData`] providing property storage. + fn core_data(&self) -> &CoreData; + + /// Get mutable access to the [`CoreData`] providing property storage. + /// + /// This should not normally be needed by user code. + #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] + fn core_data_mut(&mut self) -> &mut CoreData; + + /// Get the widget's numeric identifier + #[inline] + fn id(&self) -> WidgetId { + self.core_data().id + } + + /// Get whether the widget is disabled + #[inline] + fn is_disabled(&self) -> bool { + self.core_data().disabled + } + + /// Get 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 { + self.core_data().rect + } + + /// Get the name of the widget struct + fn widget_name(&self) -> &'static str; + + /// Erase type + fn as_widget(&self) -> &dyn WidgetConfig; + /// Erase type + fn as_widget_mut(&mut self) -> &mut dyn WidgetConfig; + + /// Construct [`InputState`] + /// + /// The `disabled` flag is inherited from parents. [`InputState::disabled`] + /// will be true if either `disabled` or `self.is_disabled()` are true. + /// + /// The error state defaults to `false` since most widgets don't support + /// this. + fn input_state(&self, mgr: &ManagerState, disabled: bool) -> InputState { + let id = self.core_data().id; + InputState { + disabled: self.core_data().disabled || disabled, + error: false, + hover: mgr.is_hovered(id), + depress: mgr.is_depressed(id), + nav_focus: mgr.nav_focus(id), + char_focus: mgr.char_focus(id), + } + } +} + +/// Listing of a widget's children +/// +/// Usually this is implemented by `derive(Widget)`, but for dynamic widgets it +/// may have to be implemented manually. Note that if the results of these +/// methods ever change, one must send [`TkAction::Reconfigure`]. +/// TODO: full reconfigure may be too slow; find a better option. +pub trait WidgetChildren: WidgetCore { + /// Get the number of child widgets + fn len(&self) -> usize; + + /// Get a reference to a child widget by index, or `None` if the index is + /// out of bounds. + /// + /// For convenience, `Index` is implemented via this method. + /// + /// Required: `index < self.len()`. + fn get(&self, index: usize) -> Option<&dyn WidgetConfig>; + + /// Mutable variant of get + /// + /// Warning: directly adjusting a widget without requiring reconfigure or + /// redraw may break the UI. If a widget is replaced, a reconfigure **must** + /// be requested. This can be done via [`Manager::send_action`]. + /// This method may be removed in the future. + fn get_mut(&mut self, index: usize) -> Option<&mut dyn WidgetConfig>; + + /// Check whether `id` is a descendant + /// + /// This function assumes that `id` is a valid widget. + #[inline] + fn is_ancestor_of(&self, id: WidgetId) -> bool { + self.find(id).is_some() + } + + /// Find a child widget by identifier + /// + /// This requires that the widget tree has already been configured by + /// [`event::ManagerState::configure`]. + /// + /// If the widget is disabled, this returns `None` without recursing children. + fn find(&self, id: WidgetId) -> Option<&dyn WidgetConfig> { + if id == self.id() { + return Some(self.as_widget()); + } else if id > self.id() { + return None; + } + + for i in 0..self.len() { + if let Some(w) = self.get(i) { + if id > w.id() { + continue; + } + return w.find(id); + } + break; + } + None + } + + /// Find a child widget by identifier + /// + /// This requires that the widget tree has already been configured by + /// [`ManagerState::configure`]. + fn find_mut(&mut self, id: WidgetId) -> Option<&mut dyn WidgetConfig> { + if id == self.id() { + return Some(self.as_widget_mut()); + } else if id > self.id() { + return None; + } + + for i in 0..self.len() { + if self.get(i).map(|w| id > w.id()).unwrap_or(true) { + continue; + } + if let Some(w) = self.get_mut(i) { + return w.find_mut(id); + } + break; + } + None + } + + /// Walk through all widgets, calling `f` once on each. + /// + /// This walk is iterative (nonconcurrent), depth-first, and always calls + /// `f` on self *after* walking through all children. + fn walk(&self, f: &mut dyn FnMut(&dyn WidgetConfig)) { + for i in 0..self.len() { + if let Some(w) = self.get(i) { + w.walk(f); + } + } + f(self.as_widget()); + } + + /// Walk through all widgets, calling `f` once on each. + /// + /// This walk is iterative (nonconcurrent), depth-first, and always calls + /// `f` on self *after* walking through all children. + fn walk_mut(&mut self, f: &mut dyn FnMut(&mut dyn WidgetConfig)) { + for i in 0..self.len() { + if let Some(w) = self.get_mut(i) { + w.walk_mut(f); + } + } + f(self.as_widget_mut()); + } +} + +/// Widget configuration +/// +/// This trait allows some configuration of widget behaviour. All items have +/// default values. This trait may be implemented by hand, or may be derived +/// with the [`derive(Widget)` macro](macros/index.html#the-derivewidget-macro) +/// by use of a `#[widget_config]` attribute. Optionally, this attribute can +/// contain parameters, e.g. `#[widget_config(key_nav = true)]`. +// TODO(specialization): provide a blanket implementation, so that users only +// need implement manually when they have something to configure. +pub trait WidgetConfig: Layout { + /// Configure widget + /// + /// Widgets are *configured* on window creation and when + /// [`TkAction::Reconfigure`] is sent. + /// + /// Configure is called before resizing (but after calculation of the + /// initial window size). This method is called after + /// a [`WidgetId`] has been assigned to self, and after `configure` has + /// been called on each child. + /// + /// The default implementation of this method does nothing. + fn configure(&mut self, _: &mut Manager) {} + + /// Is this widget navigable via Tab key? + /// + /// Defaults to `false`. + fn key_nav(&self) -> bool { + false + } + + /// Which cursor icon should be used on hover? + /// + /// Defaults to [`event::CursorIcon::Default`]. + fn cursor_icon(&self) -> event::CursorIcon { + event::CursorIcon::Default + } +} + +/// Positioning and drawing routines for widgets +/// +/// This trait contains methods concerned with positioning of contents +/// as well as low-level event handling. +/// +/// For a description of the widget size model, see [`SizeRules`]. +pub trait Layout: WidgetChildren { + /// Get size rules for the given axis. + /// + /// This method takes `&mut self` to allow local caching of child widget + /// configuration for future `size_rules` and `set_rect` calls. + /// + /// Optionally, this method may set `self.rect().size` to the widget's ideal + /// size for use by [`Layout::set_rect`] when setting alignment. + /// + /// If operating on one axis and the other is fixed, then the `other` + /// parameter is used for the fixed dimension. Additionally, one may assume + /// that `size_rules` has previously been called on the fixed axis with the + /// current widget configuration. + fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules; + + /// Adjust to the given size. + /// + /// For widgets with children, this is usually implemented via the derive + /// [macro](kas::macros). For non-parent widgets which stretch to fill + /// available space, the default implementation suffices. For non-parent + /// widgets which react to alignment, this is a little more complex to + /// implement, and can be done in one of two ways: + /// + /// 1. Shrinking to ideal area and aligning within available space (e.g. + /// `CheckBoxBare` widget) + /// 2. Filling available space and applying alignment to contents (e.g. + /// `Label` widget) + /// + /// One may assume that `size_rules` has been called for each axis with the + /// current widget configuration. + #[inline] + fn set_rect(&mut self, rect: Rect, _align: AlignHints) { + self.core_data_mut().rect = rect; + } + + /// Get translation of a child + /// + /// Children may live in a translated coordinate space relative to their + /// parent. This method returns an offset which should be *added* to a + /// coordinate to translate *into* the child's coordinate space or + /// subtracted to translate out. + /// + /// In most cases, the translation will be zero. Widgets should return + /// [`Coord::ZERO`] for non-existant children. + #[inline] + fn translation(&self, _child_index: usize) -> Coord { + Coord::ZERO + } + + /// Iterate through children in spatial order + /// + /// Returns a "range" of children, by index, in spatial order. Unlike + /// `std::ops::Range` this is inclusive and reversible, e.g. `(1, 3)` means + /// `1, 2, 3` and `(5, 2)` means `5, 4, 3, 2`. As a special case, + /// `(_, std::usize::MAX)` means the range is empty. + /// + /// Disabled widgets should return an empty range, otherwise they should + /// return a range over children in spatial order (left-to-right then + /// top-to-bottom). Widgets outside the parent's rect (i.e. popups) should + /// be excluded. + /// + /// The default implementation returns + /// `(0, WidgetChildren::len(self).wrapping_sub(1))` when not disabled + /// which should suffice for most widgets. + fn spatial_range(&self) -> (usize, usize) { + (0, WidgetChildren::len(self).wrapping_sub(1)) + } + + /// Find a widget by coordinate + /// + /// Returns the identifier of the widget containing this `coord`, if any. + /// Should only return `None` when `coord` is outside the widget's rect, + /// but this is not guaranteed. + /// + /// Implementations should: + /// + /// 1. return `None` if `!self.rect().contains(coord)` + /// 2. if, for any child (containing `coord`), `child.find_id(coord)` + /// returns `Some(id)`, return that + /// 3. otherwise, return `Some(self.id())` + /// + /// Exceptionally, a widget may deviate from this behaviour, but only when + /// the coord is within the widget's rect (example: `CheckBox` contains an + /// embedded `CheckBoxBare` and always forwards this child's id). + /// + /// This must not be called before [`Layout::set_rect`]. + #[inline] + fn find_id(&self, coord: Coord) -> Option { + if !self.rect().contains(coord) { + return None; + } + Some(self.id()) + } + + /// Draw a widget + /// + /// This method is called to draw each visible widget (and should not + /// attempt recursion on child widgets). + fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &ManagerState, disabled: bool); +} + +/// Widget trait +/// +/// This is one of a family of widget traits, all of which must be implemented +/// for a functional widget. In general, most traits will be implemented via the +/// [`derive(Widget)` macro](macros/index.html#the-derivewidget-macro). +/// +/// A [`Widget`] may be passed into a generic function via +/// `fn foo(w: &mut W)` or via +/// `fn foo(w: &mut dyn Widget)`, or, e.g. +/// `fn foo(w: &mut dyn WidgetConfig)` (note that `WidgetConfig` is the last unparameterised +/// trait in the widget trait family). +pub trait Widget: event::SendEvent {} + +impl Boxed for W { + type T = dyn Widget; + fn boxed(self) -> Box { + Box::new(self) + } +} From 39d2dc40bc9ecd8cdd3e8df306c777038c1d927e Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 29 Apr 2020 11:36:28 +0100 Subject: [PATCH 06/11] Fix Boxed --- src/traits/utils.rs | 7 ++----- src/traits/widget.rs | 5 ++--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/traits/utils.rs b/src/traits/utils.rs index bd0417140..64dad5303 100644 --- a/src/traits/utils.rs +++ b/src/traits/utils.rs @@ -22,10 +22,7 @@ impl CloneTo for T { } /// Provides a convenient `.boxed()` method on implementors -pub trait Boxed { - /// Target type (`dyn Trait`) - type T: ?Sized; - +pub trait Boxed { /// Boxing method - fn boxed(self) -> Box; + fn boxed(self) -> Box; } diff --git a/src/traits/widget.rs b/src/traits/widget.rs index 2a1635b2f..cd0428278 100644 --- a/src/traits/widget.rs +++ b/src/traits/widget.rs @@ -381,9 +381,8 @@ pub trait Layout: WidgetChildren { /// trait in the widget trait family). pub trait Widget: event::SendEvent {} -impl Boxed for W { - type T = dyn Widget; - fn boxed(self) -> Box { +impl Boxed> for W { + fn boxed(self) -> Box> { Box::new(self) } } From ebcba4d16e882dc688af0ef4e63970bd420f57cf Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 29 Apr 2020 11:39:07 +0100 Subject: [PATCH 07/11] Move MenuFrame to sub-module --- src/widget/menu.rs | 105 +------------------------------- src/widget/menu/menu_frame.rs | 109 ++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 103 deletions(-) create mode 100644 src/widget/menu/menu_frame.rs diff --git a/src/widget/menu.rs b/src/widget/menu.rs index 4b7ce12cb..8fb0aac2a 100644 --- a/src/widget/menu.rs +++ b/src/widget/menu.rs @@ -6,112 +6,11 @@ //! Menus mod menu_entry; +mod menu_frame; mod menubar; mod submenu; pub use menu_entry::{MenuEntry, MenuToggle}; +pub use menu_frame::MenuFrame; pub use menubar::MenuBar; pub use submenu::SubMenu; - -use kas::class::*; -use kas::draw::{DrawHandle, SizeHandle}; -use kas::event::Handler; -use kas::layout::{AxisInfo, Margins, SizeRules}; -use kas::prelude::*; - -/// A frame around content, plus background -#[handler(msg = ::Msg)] -#[derive(Clone, Debug, Default, Widget)] -pub struct MenuFrame { - #[widget_core] - core: CoreData, - #[widget] - pub inner: W, - m0: Size, - m1: Size, -} - -impl MenuFrame { - /// Construct a frame - #[inline] - pub fn new(inner: W) -> Self { - MenuFrame { - core: Default::default(), - inner, - m0: Size::ZERO, - m1: Size::ZERO, - } - } -} - -impl Layout for MenuFrame { - fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { - let size = size_handle.frame(); - let margins = Margins::ZERO; - let frame_rules = SizeRules::extract_fixed(axis.is_vertical(), size + size, margins); - - let child_rules = self.inner.size_rules(size_handle, axis); - let m = child_rules.margins(); - - if axis.is_horizontal() { - self.m0.0 = size.0 + m.0 as u32; - self.m1.0 = size.0 + m.1 as u32; - } else { - self.m0.1 = size.1 + m.0 as u32; - self.m1.1 = size.1 + m.1 as u32; - } - - child_rules.surrounded_by(frame_rules, true) - } - - fn set_rect(&mut self, mut rect: Rect, align: AlignHints) { - self.core.rect = rect; - rect.pos += self.m0; - rect.size -= self.m0 + self.m1; - self.inner.set_rect(rect, align); - } - - #[inline] - fn find_id(&self, coord: Coord) -> Option { - if !self.rect().contains(coord) { - return None; - } - self.inner.find_id(coord).or(Some(self.id())) - } - - fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { - draw_handle.menu_frame(self.core_data().rect); - let disabled = disabled || self.is_disabled(); - self.inner.draw(draw_handle, mgr, disabled); - } -} - -impl HasBool for MenuFrame { - fn get_bool(&self) -> bool { - self.inner.get_bool() - } - - fn set_bool(&mut self, state: bool) -> TkAction { - self.inner.set_bool(state) - } -} - -impl HasText for MenuFrame { - fn get_text(&self) -> &str { - self.inner.get_text() - } - - fn set_cow_string(&mut self, text: CowString) -> TkAction { - self.inner.set_cow_string(text) - } -} - -impl Editable for MenuFrame { - fn is_editable(&self) -> bool { - self.inner.is_editable() - } - - fn set_editable(&mut self, editable: bool) { - self.inner.set_editable(editable); - } -} diff --git a/src/widget/menu/menu_frame.rs b/src/widget/menu/menu_frame.rs new file mode 100644 index 000000000..928547182 --- /dev/null +++ b/src/widget/menu/menu_frame.rs @@ -0,0 +1,109 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License in the LICENSE-APACHE file or at: +// https://www.apache.org/licenses/LICENSE-2.0 + +//! Menus + +use kas::class::*; +use kas::draw::{DrawHandle, SizeHandle}; +use kas::event::Handler; +use kas::layout::{AxisInfo, Margins, SizeRules}; +use kas::prelude::*; + +/// A frame around content, plus background +#[handler(msg = ::Msg)] +#[derive(Clone, Debug, Default, Widget)] +pub struct MenuFrame { + #[widget_core] + core: CoreData, + #[widget] + pub inner: W, + m0: Size, + m1: Size, +} + +impl MenuFrame { + /// Construct a frame + #[inline] + pub fn new(inner: W) -> Self { + MenuFrame { + core: Default::default(), + inner, + m0: Size::ZERO, + m1: Size::ZERO, + } + } +} + +impl Layout for MenuFrame { + fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { + let size = size_handle.frame(); + let margins = Margins::ZERO; + let frame_rules = SizeRules::extract_fixed(axis.is_vertical(), size + size, margins); + + let child_rules = self.inner.size_rules(size_handle, axis); + let m = child_rules.margins(); + + if axis.is_horizontal() { + self.m0.0 = size.0 + m.0 as u32; + self.m1.0 = size.0 + m.1 as u32; + } else { + self.m0.1 = size.1 + m.0 as u32; + self.m1.1 = size.1 + m.1 as u32; + } + + child_rules.surrounded_by(frame_rules, true) + } + + fn set_rect(&mut self, mut rect: Rect, align: AlignHints) { + self.core.rect = rect; + rect.pos += self.m0; + rect.size -= self.m0 + self.m1; + self.inner.set_rect(rect, align); + } + + #[inline] + fn find_id(&self, coord: Coord) -> Option { + if !self.rect().contains(coord) { + return None; + } + self.inner.find_id(coord).or(Some(self.id())) + } + + fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + draw_handle.menu_frame(self.core_data().rect); + let disabled = disabled || self.is_disabled(); + self.inner.draw(draw_handle, mgr, disabled); + } +} + +impl HasBool for MenuFrame { + fn get_bool(&self) -> bool { + self.inner.get_bool() + } + + fn set_bool(&mut self, state: bool) -> TkAction { + self.inner.set_bool(state) + } +} + +impl HasText for MenuFrame { + fn get_text(&self) -> &str { + self.inner.get_text() + } + + fn set_cow_string(&mut self, text: CowString) -> TkAction { + self.inner.set_cow_string(text) + } +} + +impl Editable for MenuFrame { + fn is_editable(&self) -> bool { + self.inner.is_editable() + } + + fn set_editable(&mut self, editable: bool) { + self.inner.set_editable(editable); + } +} From 6d6958d7ac262d21a88e685d3bb17509f61a20a9 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 29 Apr 2020 11:48:48 +0100 Subject: [PATCH 08/11] Add a Menu trait --- src/widget/menu.rs | 137 ++++++++++++++++++++++++++++++++++ src/widget/menu/menu_entry.rs | 6 ++ src/widget/menu/menubar.rs | 14 ++-- src/widget/menu/submenu.rs | 22 +++--- src/widget/separator.rs | 4 + 5 files changed, 166 insertions(+), 17 deletions(-) diff --git a/src/widget/menu.rs b/src/widget/menu.rs index 8fb0aac2a..1c9bc8012 100644 --- a/src/widget/menu.rs +++ b/src/widget/menu.rs @@ -14,3 +14,140 @@ pub use menu_entry::{MenuEntry, MenuToggle}; pub use menu_frame::MenuFrame; pub use menubar::MenuBar; pub use submenu::SubMenu; + +use kas::draw::{DrawHandle, SizeHandle}; +use kas::event::{Event, Manager, Response}; +use kas::layout::{AxisInfo, SizeRules}; +use kas::prelude::*; + +/// Trait governing menus, sub-menus and menu-entries +pub trait Menu: Widget {} + +impl WidgetCore for Box> { + fn as_any(&self) -> &dyn std::any::Any { + self.as_ref().as_any() + } + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self.as_mut().as_any_mut() + } + + fn core_data(&self) -> &CoreData { + self.as_ref().core_data() + } + fn core_data_mut(&mut self) -> &mut CoreData { + self.as_mut().core_data_mut() + } + + fn widget_name(&self) -> &'static str { + self.as_ref().widget_name() + } + + fn as_widget(&self) -> &dyn WidgetConfig { + self.as_ref().as_widget() + } + fn as_widget_mut(&mut self) -> &mut dyn WidgetConfig { + self.as_mut().as_widget_mut() + } +} + +impl WidgetChildren for Box> { + fn len(&self) -> usize { + self.as_ref().len() + } + fn get(&self, index: usize) -> Option<&dyn WidgetConfig> { + self.as_ref().get(index) + } + fn get_mut(&mut self, index: usize) -> Option<&mut dyn WidgetConfig> { + self.as_mut().get_mut(index) + } + + fn find(&self, id: WidgetId) -> Option<&dyn WidgetConfig> { + self.as_ref().find(id) + } + fn find_mut(&mut self, id: WidgetId) -> Option<&mut dyn WidgetConfig> { + self.as_mut().find_mut(id) + } + + fn walk(&self, f: &mut dyn FnMut(&dyn WidgetConfig)) { + self.as_ref().walk(f); + } + fn walk_mut(&mut self, f: &mut dyn FnMut(&mut dyn WidgetConfig)) { + self.as_mut().walk_mut(f); + } +} + +impl WidgetConfig for Box> { + fn configure(&mut self, mgr: &mut Manager) { + self.as_mut().configure(mgr); + } + + fn key_nav(&self) -> bool { + self.as_ref().key_nav() + } + fn cursor_icon(&self) -> event::CursorIcon { + self.as_ref().cursor_icon() + } +} + +impl Layout for Box> { + fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { + self.as_mut().size_rules(size_handle, axis) + } + + fn set_rect(&mut self, rect: Rect, align: AlignHints) { + self.as_mut().set_rect(rect, align); + } + + fn find_id(&self, coord: Coord) -> Option { + self.as_ref().find_id(coord) + } + + fn draw(&self, draw_handle: &mut dyn DrawHandle, mgr: &event::ManagerState, disabled: bool) { + self.as_ref().draw(draw_handle, mgr, disabled); + } +} + +impl event::Handler for Box> { + type Msg = M; + + fn activation_via_press(&self) -> bool { + self.as_ref().activation_via_press() + } + + fn handle(&mut self, mgr: &mut Manager, event: Event) -> Response { + self.as_mut().handle(mgr, event) + } +} + +impl event::SendEvent for Box> { + fn send(&mut self, mgr: &mut Manager, id: WidgetId, event: Event) -> Response { + self.as_mut().send(mgr, id, event) + } +} + +impl Widget for Box> {} + +impl Menu for Box> {} + +impl Clone for Box> { + fn clone(&self) -> Self { + #[cfg(feature = "nightly")] + unsafe { + let mut x = Box::new_uninit(); + self.clone_to(x.as_mut_ptr()); + x.assume_init() + } + + // Run-time failure is not ideal — but we would hit compile-issues which + // don't necessarily correspond to actual usage otherwise due to + // `derive(Clone)` on any widget produced by `make_widget!`. + #[cfg(not(feature = "nightly"))] + panic!("Clone for Box only supported on nightly"); + } +} + +impl Boxed> for M { + fn boxed(self) -> Box> { + Box::new(self) + } +} diff --git a/src/widget/menu/menu_entry.rs b/src/widget/menu/menu_entry.rs index 490b5c834..e478ec6de 100644 --- a/src/widget/menu/menu_entry.rs +++ b/src/widget/menu/menu_entry.rs @@ -7,6 +7,7 @@ use std::fmt::{self, Debug}; +use super::Menu; use kas::class::{HasBool, HasText}; use kas::draw::{DrawHandle, SizeHandle, TextClass}; use kas::event::{Event, Manager, Response, VoidMsg}; @@ -89,6 +90,8 @@ impl event::Handler for MenuEntry { } } +impl Menu for MenuEntry {} + /// A menu entry which can be toggled #[handler(msg = M, generics = <> where M: From)] #[derive(Clone, Default, Widget)] @@ -220,6 +223,9 @@ impl kas::Layout for MenuToggle { self.label.draw(draw_handle, mgr, state.disabled); } } + +impl> Menu for MenuToggle {} + impl HasBool for MenuToggle { #[inline] fn get_bool(&self) -> bool { diff --git a/src/widget/menu/menubar.rs b/src/widget/menu/menubar.rs index 66585ce33..98c1e16cc 100644 --- a/src/widget/menu/menubar.rs +++ b/src/widget/menu/menubar.rs @@ -7,7 +7,7 @@ use std::time::Duration; -use super::SubMenu; +use super::{Menu, SubMenu}; use kas::draw::{DrawHandle, SizeHandle}; use kas::event::{Event, GrabMode, Handler, Manager, Response, SendEvent}; use kas::layout::{AxisInfo, SizeRules}; @@ -20,7 +20,7 @@ use kas::widget::List; /// menus. #[handler(noauto)] #[derive(Clone, Debug, Widget)] -pub struct MenuBar { +pub struct MenuBar { #[widget_core] core: CoreData, #[widget] @@ -30,14 +30,14 @@ pub struct MenuBar { delayed_open: Option, } -impl MenuBar { +impl MenuBar { /// Construct pub fn new(menus: Vec>) -> Self { MenuBar::new_with_direction(D::default(), menus) } } -impl MenuBar { +impl MenuBar { /// Construct pub fn new_with_direction(direction: D, menus: Vec>) -> Self { MenuBar { @@ -50,7 +50,7 @@ impl MenuBar { } // NOTE: we could use layout(single) except for alignment -impl Layout for MenuBar { +impl Layout for MenuBar { fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { self.bar.size_rules(size_handle, axis) } @@ -76,7 +76,7 @@ impl Layout for MenuBar { self.bar.draw(draw_handle, mgr, disabled); } } -impl, M> event::Handler for MenuBar { +impl, M> event::Handler for MenuBar { type Msg = M; fn handle(&mut self, mgr: &mut Manager, event: Event) -> Response { @@ -179,7 +179,7 @@ impl, M> event::Handler for MenuBar { } } -impl event::SendEvent for MenuBar { +impl event::SendEvent for MenuBar { fn send(&mut self, mgr: &mut Manager, id: WidgetId, event: Event) -> Response { if self.is_disabled() { return Response::Unhandled(event); diff --git a/src/widget/menu/submenu.rs b/src/widget/menu/submenu.rs index 95ccdded1..c09cdf0e9 100644 --- a/src/widget/menu/submenu.rs +++ b/src/widget/menu/submenu.rs @@ -5,7 +5,7 @@ //! Sub-menu -use super::MenuFrame; +use super::{Menu, MenuFrame}; use kas::class::HasText; use kas::draw::{DrawHandle, SizeHandle, TextClass}; use kas::event::{Event, Manager, NavKey, Response}; @@ -18,7 +18,7 @@ use kas::WindowId; #[widget(config(key_nav = true))] #[handler(noauto)] #[derive(Clone, Debug, Widget)] -pub struct SubMenu { +pub struct SubMenu { #[widget_core] core: CoreData, direction: D, @@ -29,7 +29,7 @@ pub struct SubMenu { popup_id: Option, } -impl SubMenu { +impl SubMenu { /// Construct a sub-menu #[inline] pub fn new>(label: S, list: Vec) -> Self { @@ -37,7 +37,7 @@ impl SubMenu { } } -impl SubMenu { +impl SubMenu { /// Construct a sub-menu, opening to the right // NOTE: this is used since we can't infer direction of a boxed SubMenu. // Consider only accepting an enum of special menu widgets? @@ -48,7 +48,7 @@ impl SubMenu { } } -impl SubMenu { +impl SubMenu { /// Construct a sub-menu, opening downwards #[inline] pub fn down>(label: S, list: Vec) -> Self { @@ -56,7 +56,7 @@ impl SubMenu { } } -impl SubMenu { +impl SubMenu { /// Construct a sub-menu #[inline] pub fn new_with_direction>(direction: D, label: S, list: Vec) -> Self { @@ -91,7 +91,7 @@ impl SubMenu { } } -impl kas::Layout for SubMenu { +impl kas::Layout for SubMenu { fn size_rules(&mut self, size_handle: &mut dyn SizeHandle, axis: AxisInfo) -> SizeRules { let size = size_handle.menu_frame(); self.label_off = size.into(); @@ -118,7 +118,7 @@ impl kas::Layout for SubMenu { } } -impl> event::Handler for SubMenu { +impl> event::Handler for SubMenu { type Msg = M; fn handle(&mut self, mgr: &mut Manager, event: Event) -> Response { @@ -155,7 +155,7 @@ impl> event::Handler for SubMenu { } } -impl event::SendEvent for SubMenu { +impl event::SendEvent for SubMenu { fn send(&mut self, mgr: &mut Manager, id: WidgetId, event: Event) -> Response { if self.is_disabled() { return Response::Unhandled(event); @@ -207,7 +207,9 @@ impl event::SendEvent for SubMenu { } } -impl HasText for SubMenu { +impl Menu for SubMenu {} + +impl HasText for SubMenu { fn get_text(&self) -> &str { &self.label } diff --git a/src/widget/separator.rs b/src/widget/separator.rs index 14d5d6cbe..65793cada 100644 --- a/src/widget/separator.rs +++ b/src/widget/separator.rs @@ -11,6 +11,7 @@ use std::marker::PhantomData; use kas::draw::{DrawHandle, SizeHandle}; use kas::layout::{AxisInfo, SizeRules}; use kas::prelude::*; +use kas::widget::Menu; /// A separator /// @@ -58,3 +59,6 @@ impl Layout for Separator { draw_handle.separator(self.core.rect); } } + +/// A separator is a valid menu widget +impl Menu for Separator {} From 1020174ed5421917c035988145e3e73e270c6c1e Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 29 Apr 2020 12:05:48 +0100 Subject: [PATCH 09/11] Add Menu::menu_path and use in MenuBar This makes Event::OpenPopup and ClosePopup obsolete. --- src/event/events.rs | 10 ---------- src/widget/menu.rs | 19 +++++++++++++++++-- src/widget/menu/menubar.rs | 22 ++++++++++++++++++---- src/widget/menu/submenu.rs | 37 ++++++++++++++++++++++++++++++------- 4 files changed, 65 insertions(+), 23 deletions(-) diff --git a/src/event/events.rs b/src/event/events.rs index 4fb2d893b..b2801a7d8 100644 --- a/src/event/events.rs +++ b/src/event/events.rs @@ -122,16 +122,6 @@ pub enum Event { /// A user-defined payload is passed. Interpretation of this payload is /// user-defined and unfortunately not type safe. HandleUpdate { handle: UpdateHandle, payload: u64 }, - /// Open popup / menu - /// - /// This is a specific command from a parent, e.g. [`kas::widget::MenuBar`]. - /// Most widgets can ignore this, even if they have a pop-up. - OpenPopup, - /// Close popup / menu - /// - /// This is a specific command from a parent, e.g. [`kas::widget::MenuBar`]. - /// Most widgets can ignore this, even if they have a pop-up. - ClosePopup, /// Notification that a new popup has been created /// /// This is sent to the parent of each open popup when a new popup is diff --git a/src/widget/menu.rs b/src/widget/menu.rs index 1c9bc8012..8010ac5a1 100644 --- a/src/widget/menu.rs +++ b/src/widget/menu.rs @@ -5,6 +5,8 @@ //! Menus +use std::ops::DerefMut; + mod menu_entry; mod menu_frame; mod menubar; @@ -21,7 +23,16 @@ use kas::layout::{AxisInfo, SizeRules}; use kas::prelude::*; /// Trait governing menus, sub-menus and menu-entries -pub trait Menu: Widget {} +pub trait Menu: Widget { + /// Open or close a sub-menu, including parents + /// + /// Given `Some(id) = target`, the sub-menu with this `id` should open its + /// menu; if it has child-menus, these should close; and if any ancestors + /// are menus, these should open. + /// + /// `target == None` implies that all menus should close. + fn menu_path(&mut self, _mgr: &mut Manager, _target: Option) {} +} impl WidgetCore for Box> { fn as_any(&self) -> &dyn std::any::Any { @@ -127,7 +138,11 @@ impl event::SendEvent for Box> { impl Widget for Box> {} -impl Menu for Box> {} +impl Menu for Box> { + fn menu_path(&mut self, mgr: &mut Manager, target: Option) { + self.deref_mut().menu_path(mgr, target) + } +} impl Clone for Box> { fn clone(&self) -> Self { diff --git a/src/widget/menu/menubar.rs b/src/widget/menu/menubar.rs index 98c1e16cc..4050dcbcb 100644 --- a/src/widget/menu/menubar.rs +++ b/src/widget/menu/menubar.rs @@ -84,7 +84,7 @@ impl, M> event::Handler for MenuBar { Event::TimerUpdate => { if let Some(id) = self.delayed_open { self.delayed_open = None; - return self.send(mgr, id, Event::OpenPopup); + self.menu_path(mgr, Some(id)); } } Event::PressStart { @@ -144,15 +144,21 @@ impl, M> event::Handler for MenuBar { let id = end_id.unwrap(); if self.rect().contains(coord) { + // end coordinate is on the menubar if !self.opening { - // TODO: click on title should close menu, - // but we don't have a mechanism to do that! + for i in 0..self.bar.len() { + if self.bar[i].id() == id { + self.bar[i].menu_path(mgr, None); + } + } } } else { + // not on the menubar, thus on a sub-menu return self.send(mgr, id, Event::Activate); } } else { - // TODO: drag-click off menu should close menu + // not on the menu + self.menu_path(mgr, None); } } /* TODO @@ -195,3 +201,11 @@ impl event::SendEvent for MenuBar { self.handle(mgr, event) } } + +impl Menu for MenuBar { + fn menu_path(&mut self, mgr: &mut Manager, target: Option) { + for i in 0..self.bar.len() { + self.bar[i].menu_path(mgr, target); + } + } +} diff --git a/src/widget/menu/submenu.rs b/src/widget/menu/submenu.rs index c09cdf0e9..03a3f0b5f 100644 --- a/src/widget/menu/submenu.rs +++ b/src/widget/menu/submenu.rs @@ -123,16 +123,11 @@ impl> event::Handler for SubMenu { fn handle(&mut self, mgr: &mut Manager, event: Event) -> Response { match event { - Event::Activate | Event::OpenPopup => { + Event::Activate => { if self.popup_id.is_none() { self.open_menu(mgr); } } - Event::ClosePopup => { - if let Some(id) = self.popup_id { - mgr.close_window(id); - } - } Event::NewPopup(id) => { if self.popup_id.is_some() && !self.is_ancestor_of(id) { self.close_menu(mgr); @@ -207,7 +202,35 @@ impl event::SendEvent for SubMenu { } } -impl Menu for SubMenu {} +impl Menu for SubMenu { + fn menu_path(&mut self, mgr: &mut Manager, target: Option) { + match target { + Some(id) if id == self.id() => { + if self.popup_id.is_some() { + for i in 0..self.list.inner.len() { + self.list.inner[i].menu_path(mgr, None); + } + } else { + self.open_menu(mgr); + } + } + Some(id) if self.is_ancestor_of(id) => { + self.open_menu(mgr); + for i in 0..self.list.inner.len() { + self.list.inner[i].menu_path(mgr, target); + } + } + _ => { + if self.popup_id.is_some() { + for i in 0..self.list.inner.len() { + self.list.inner[i].menu_path(mgr, None); + } + self.close_menu(mgr); + } + } + } + } +} impl HasText for SubMenu { fn get_text(&self) -> &str { From 855cc62448a93fa6154d01b1dcf93b6f2b086d38 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 29 Apr 2020 12:49:42 +0100 Subject: [PATCH 10/11] Move SubMenu::menu_is_open to Menu trait; improve key nav --- src/widget/menu.rs | 12 +++++++++++- src/widget/menu/menubar.rs | 18 ++++++++++++------ src/widget/menu/submenu.rs | 31 ++++++++++++++++++++----------- 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/src/widget/menu.rs b/src/widget/menu.rs index 8010ac5a1..96e5f2fb7 100644 --- a/src/widget/menu.rs +++ b/src/widget/menu.rs @@ -5,7 +5,7 @@ //! Menus -use std::ops::DerefMut; +use std::ops::{Deref, DerefMut}; mod menu_entry; mod menu_frame; @@ -24,6 +24,13 @@ use kas::prelude::*; /// Trait governing menus, sub-menus and menu-entries pub trait Menu: Widget { + /// Report whether one's own menu is open + /// + /// By default, this is `false`. + fn menu_is_open(&self) -> bool { + false + } + /// Open or close a sub-menu, including parents /// /// Given `Some(id) = target`, the sub-menu with this `id` should open its @@ -139,6 +146,9 @@ impl event::SendEvent for Box> { impl Widget for Box> {} impl Menu for Box> { + fn menu_is_open(&self) -> bool { + self.deref().menu_is_open() + } fn menu_path(&mut self, mgr: &mut Manager, target: Option) { self.deref_mut().menu_path(mgr, target) } diff --git a/src/widget/menu/menubar.rs b/src/widget/menu/menubar.rs index 4050dcbcb..ea219abf8 100644 --- a/src/widget/menu/menubar.rs +++ b/src/widget/menu/menubar.rs @@ -9,7 +9,7 @@ use std::time::Duration; use super::{Menu, SubMenu}; use kas::draw::{DrawHandle, SizeHandle}; -use kas::event::{Event, GrabMode, Handler, Manager, Response, SendEvent}; +use kas::event::{Event, GrabMode, Handler, Manager, NavKey, Response, SendEvent}; use kas::layout::{AxisInfo, SizeRules}; use kas::prelude::*; use kas::widget::List; @@ -161,7 +161,6 @@ impl, M> event::Handler for MenuBar { self.menu_path(mgr, None); } } - /* TODO Event::NavKey(key) => { // Arrow keys can switch to the next / previous menu. let is_vert = self.bar.direction().is_vertical(); @@ -174,11 +173,18 @@ impl, M> event::Handler for MenuBar { key => return Response::Unhandled(Event::NavKey(key)), }; - let index = ? - let id = self.bar[index].id(); - return self.send(mgr, id, Event::OpenPopup); + for i in 0..self.bar.len() { + if self.bar[i].menu_is_open() { + let index = if reverse { i.wrapping_sub(1) } else { i + 1 }; + if index < self.bar.len() { + self.bar[i].menu_path(mgr, None); + let w = &mut self.bar[index]; + w.menu_path(mgr, Some(w.id())); + } + break; + } + } } - */ e => return Response::Unhandled(e), } Response::None diff --git a/src/widget/menu/submenu.rs b/src/widget/menu/submenu.rs index 03a3f0b5f..635d7db68 100644 --- a/src/widget/menu/submenu.rs +++ b/src/widget/menu/submenu.rs @@ -70,9 +70,6 @@ impl SubMenu { } } - pub(crate) fn menu_is_open(&self) -> bool { - self.popup_id.is_some() - } fn open_menu(&mut self, mgr: &mut Manager) { if self.popup_id.is_none() { let id = mgr.add_popup(kas::Popup { @@ -203,21 +200,33 @@ impl event::SendEvent for SubMenu { } impl Menu for SubMenu { + fn menu_is_open(&self) -> bool { + self.popup_id.is_some() + } + fn menu_path(&mut self, mgr: &mut Manager, target: Option) { match target { - Some(id) if id == self.id() => { + Some(id) if self.is_ancestor_of(id) => { if self.popup_id.is_some() { + // We should close other sub-menus before opening + let mut child = None; for i in 0..self.list.inner.len() { - self.list.inner[i].menu_path(mgr, None); + if self.list.inner[i].is_ancestor_of(id) { + child = Some(i); + } else { + self.list.inner[i].menu_path(mgr, None); + } + } + if let Some(i) = child { + self.list.inner[i].menu_path(mgr, target); } } else { self.open_menu(mgr); - } - } - Some(id) if self.is_ancestor_of(id) => { - self.open_menu(mgr); - for i in 0..self.list.inner.len() { - self.list.inner[i].menu_path(mgr, target); + if id != self.id() { + for i in 0..self.list.inner.len() { + self.list.inner[i].menu_path(mgr, target); + } + } } } _ => { From d56162e8248cf355f9adc6ec01b25f138e5e60a9 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Wed, 29 Apr 2020 13:08:54 +0100 Subject: [PATCH 11/11] More tweaks to the MenuBar --- src/widget/menu/menubar.rs | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/widget/menu/menubar.rs b/src/widget/menu/menubar.rs index ea219abf8..2293d868d 100644 --- a/src/widget/menu/menubar.rs +++ b/src/widget/menu/menubar.rs @@ -14,6 +14,8 @@ use kas::layout::{AxisInfo, SizeRules}; use kas::prelude::*; use kas::widget::List; +const DELAY: Duration = Duration::from_millis(200); + /// A menu-bar /// /// This widget houses a sequence of menu buttons, allowing input actions across @@ -109,14 +111,14 @@ impl, M> event::Handler for MenuBar { if !w.menu_is_open() { self.opening = true; self.delayed_open = Some(id); - mgr.update_on_timer(Duration::from_millis(100), self.id()); + mgr.update_on_timer(DELAY, self.id()); } break; } } } else { self.delayed_open = Some(start_id); - mgr.update_on_timer(Duration::from_millis(100), self.id()); + mgr.update_on_timer(DELAY, self.id()); } } } else { @@ -127,12 +129,11 @@ impl, M> event::Handler for MenuBar { Event::PressMove { source, cur_id, .. } => { if let Some(w) = cur_id.and_then(|id| self.find(id)) { if w.key_nav() { - // TODO: potentially this should close a sibling's submenu let id = cur_id.unwrap(); mgr.set_grab_depress(source, Some(id)); mgr.set_nav_focus(id); self.delayed_open = Some(id); - mgr.update_on_timer(Duration::from_millis(300), self.id()); + mgr.update_on_timer(DELAY, self.id()); } } else { mgr.set_grab_depress(source, None); @@ -146,6 +147,7 @@ impl, M> event::Handler for MenuBar { if self.rect().contains(coord) { // end coordinate is on the menubar if !self.opening { + self.delayed_open = None; for i in 0..self.bar.len() { if self.bar[i].id() == id { self.bar[i].menu_path(mgr, None); @@ -154,10 +156,12 @@ impl, M> event::Handler for MenuBar { } } else { // not on the menubar, thus on a sub-menu + self.delayed_open = None; return self.send(mgr, id, Event::Activate); } } else { // not on the menu + self.delayed_open = None; self.menu_path(mgr, None); } } @@ -177,6 +181,7 @@ impl, M> event::Handler for MenuBar { if self.bar[i].menu_is_open() { let index = if reverse { i.wrapping_sub(1) } else { i + 1 }; if index < self.bar.len() { + self.delayed_open = None; self.bar[i].menu_path(mgr, None); let w = &mut self.bar[index]; w.menu_path(mgr, Some(w.id())); @@ -210,8 +215,23 @@ impl event::SendEvent for MenuBar { impl Menu for MenuBar { fn menu_path(&mut self, mgr: &mut Manager, target: Option) { - for i in 0..self.bar.len() { - self.bar[i].menu_path(mgr, target); + if let Some(id) = target { + // We should close other sub-menus before opening + let mut child = None; + for i in 0..self.bar.len() { + if self.bar[i].is_ancestor_of(id) { + child = Some(i); + } else { + self.bar[i].menu_path(mgr, None); + } + } + if let Some(i) = child { + self.bar[i].menu_path(mgr, target); + } + } else { + for i in 0..self.bar.len() { + self.bar[i].menu_path(mgr, None); + } } } }