Skip to content

Commit

Permalink
Merge pull request #405 from kas-gui/work1
Browse files Browse the repository at this point in the history
Events::recurse_range, kas_core::Popup, EventState::depress_with_key
  • Loading branch information
dhardy authored Aug 30, 2023
2 parents f342d7a + 512edb0 commit e449c6a
Show file tree
Hide file tree
Showing 56 changed files with 803 additions and 808 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ nightly = ["min_spec"]
# Enable dynamic linking (faster linking via an extra run-time dependency):
dynamic = ["dep:kas-dylib"]

# Use min_specialization (enables accelerator underlining for AccelLabel)
# Use min_specialization (enables access key underlining for AccessLabel)
min_spec = ["kas-widgets/min_spec"]
# Use full specialization
spec = ["min_spec", "kas-core/spec"]
Expand Down
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Thirdly (and no less significantly), the release added pop-up menus, including
fixes for overlapping graphics and robust event handling (including delayed
opening of sub-menus and navigation via tab and arrow keys).

Fourthly, it added Alt-accelerator keys, including visible labels when Alt is
Fourthly, it added access keys, including visible labels when Alt is
depressed and locality to the visible pop-up layer.

Small additions included recursive disabled states for all widgets and an error
Expand Down
21 changes: 0 additions & 21 deletions crates/kas-core/src/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
//! These traits provide generic ways to interact with common widget properties,
//! e.g. to read the text of a `Label` or set the state of a `CheckBox`.

use crate::text::AccelString;
use crate::Action;

/// Read / write a boolean value
Expand Down Expand Up @@ -70,23 +69,3 @@ pub trait HasFormatted {
fn set_formatted_string(&mut self, text: FormattedString) -> Action;
}
*/

/// Set a control label
///
/// Control labels do not support rich-text formatting but do support
/// accelerator keys, identified via a `&` prefix (e.g. `&File`).
pub trait SetAccel {
/// Set text
///
/// This method supports [`AccelString`], `String` and `&str` as input.
/// The latter are parsed for accel keys identified by `&` prefix.
fn set_accel<T: Into<AccelString>>(&mut self, accel: T) -> Action
where
Self: Sized,
{
self.set_accel_string(accel.into())
}

/// Set accel string
fn set_accel_string(&mut self, accel: AccelString) -> Action;
}
25 changes: 1 addition & 24 deletions crates/kas-core/src/core/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@

//! Widget data types

#[allow(unused)] use super::Widget;
use super::WidgetId;
#[allow(unused)] use super::{Layout, Widget};
use crate::dir::Direction;
#[allow(unused)] use crate::event::EventCx;
use crate::geom::Rect;

#[cfg(feature = "winit")] pub use winit::window::Icon;
Expand Down Expand Up @@ -52,24 +50,3 @@ impl Clone for CoreData {
}
}
}

/// A widget which escapes its parent's rect
///
/// A pop-up is a special widget drawn either as a layer over the existing
/// window or in a new borderless window. It should be precisely positioned
/// *next to* it's `parent`'s `rect`, in the specified `direction` (or, if not
/// possible, in the opposite direction).
///
/// A pop-up is in some ways an ordinary child widget and in some ways not.
/// The pop-up widget should be a permanent child of its parent, but is not
/// visible until [`EventCx::add_popup`] is called.
///
/// A pop-up widget's rect is not contained by its parent, therefore the parent
/// must not call any [`Layout`] methods on the pop-up (whether or not it is
/// visible). The window is responsible for calling these methods.
#[derive(Clone, Debug)]
pub struct Popup {
pub id: WidgetId,
pub parent: WidgetId,
pub direction: Direction,
}
7 changes: 3 additions & 4 deletions crates/kas-core/src/core/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub fn _configure<W: Widget + Events<Data = <W as Widget>::Data>>(
) {
widget.pre_configure(cx, id);

for index in 0..widget.num_children() {
for index in widget.recurse_range() {
let id = widget.make_child_id(index);
if id.is_valid() {
widget
Expand All @@ -38,10 +38,9 @@ pub fn _update<W: Widget + Events<Data = <W as Widget>::Data>>(
data: &<W as Widget>::Data,
) {
widget.update(cx, data);
let start = cx.recurse_start.take().unwrap_or(0);
let end = cx.recurse_end.take().unwrap_or(widget.num_children());
let range = widget.recurse_range();
let mut node = widget.as_node(data);
for index in start..end {
for index in range {
node.for_child(index, |mut node| node._update(cx));
}
}
Expand Down
90 changes: 65 additions & 25 deletions crates/kas-core/src/core/widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use kas_macros::autoimpl;
/// [`Events::pre_configure`] which assigns `self.core.id = id`.
///
/// [`#widget`]: macros::widget
pub trait Events: Sized {
pub trait Events: Layout + Sized {
/// Input data type
///
/// This type must match [`Widget::Data`]. When using the `#widget` macro,
Expand All @@ -33,6 +33,21 @@ pub trait Events: Sized {
/// [`#widget`]: macros::widget
type Data;

/// Recursion range
///
/// Methods `pre_configure`, `configure` and `update` all recurse over the
/// widget tree. This method may be used to limit that recursion to a range
/// of children.
///
/// Widgets do not need to be configured or updated if not visible, but in
/// this case must be configured when made visible (for example, the `Stack`
/// widget configures only the visible page).
///
/// Default implementation: `0..self.num_children()`.
fn recurse_range(&self) -> std::ops::Range<usize> {
0..self.num_children()
}

/// Pre-configuration
///
/// This method is called before children are configured to assign a
Expand All @@ -51,6 +66,9 @@ pub trait Events: Sized {
/// [`Layout::size_rules`] or [`Layout::set_rect`]. Configuration may be
/// repeated and may be used as a mechanism to change a child's [`WidgetId`].
///
/// It is possible to limit which children get configured via
/// [`Self::recurse_range`].
///
/// This method may be used to configure event handling and to load
/// resources, including resources affecting [`Layout::size_rules`].
///
Expand All @@ -74,11 +92,9 @@ pub trait Events: Sized {
///
/// This method is called on the parent widget before children get updated.
///
/// This method may call [`ConfigCx::restrict_recursion_to`].
/// It is possible to limit which children get updated via
/// [`Self::recurse_range`].
/// Widgets should be updated even if their data is `()` or is unchanged.
/// The only valid reasons not to update a child is because (a) it is not
/// visible (for example, the `Stack` widget updates only the visible page)
/// or (b) another method is used to update the child.
///
/// The default implementation does nothing.
fn update(&mut self, cx: &mut ConfigCx, data: &Self::Data) {
Expand Down Expand Up @@ -294,21 +310,19 @@ pub enum NavAdvance {
///
/// impl_scope! {
/// /// A text label
/// #[widget {
/// Data = ();
/// }]
/// pub struct AccelLabel {
/// #[widget]
/// pub struct AccessLabel {
/// core: widget_core!(),
/// class: TextClass,
/// label: Text<AccelString>,
/// label: Text<AccessString>,
/// }
///
/// impl Self {
/// /// Construct from `label`
/// pub fn new(label: impl Into<AccelString>) -> Self {
/// AccelLabel {
/// pub fn new(label: impl Into<AccessString>) -> Self {
/// AccessLabel {
/// core: Default::default(),
/// class: TextClass::AccelLabel(true),
/// class: TextClass::AccessLabel(true),
/// label: Text::new(label.into()),
/// }
/// }
Expand All @@ -319,8 +333,8 @@ pub enum NavAdvance {
/// self
/// }
///
/// /// Get the accelerator key
/// pub fn accel_key(&self) -> Option<&event::Key> {
/// /// Get the access key
/// pub fn access_key(&self) -> Option<&event::Key> {
/// self.label.text().key()
/// }
/// }
Expand All @@ -340,6 +354,26 @@ pub enum NavAdvance {
/// draw.text_effects(self.rect(), &self.label, self.class);
/// }
/// }
///
/// impl Events for Self {
/// type Data = ();
///
/// fn configure(&mut self, cx: &mut ConfigCx) {
/// if let Some(key) = self.label.text().key() {
/// cx.add_access_key(self.id_ref(), key.clone());
/// }
/// }
///
/// fn handle_event(&mut self, cx: &mut EventCx, _: &Self::Data, event: Event) -> Response {
/// match event {
/// Event::Command(cmd, code) if cmd.is_activate() => {
/// cx.push(kas::message::Activate(code));
/// Response::Used
/// }
/// _ => Response::Unused
/// }
/// }
/// }
/// }
///
/// impl_scope! {
Expand All @@ -352,35 +386,39 @@ pub enum NavAdvance {
/// pub struct TextButton<M: Clone + Debug + 'static> {
/// core: widget_core!(),
/// #[widget]
/// label: AccelLabel,
/// label: AccessLabel,
/// message: M,
/// }
///
/// impl Self {
/// /// Construct a button with given `label`
/// pub fn new(label: impl Into<AccelString>, message: M) -> Self {
/// pub fn new(label: impl Into<AccessString>, message: M) -> Self {
/// TextButton {
/// core: Default::default(),
/// label: AccelLabel::new(label).with_class(TextClass::Button),
/// label: AccessLabel::new(label).with_class(TextClass::Button),
/// message,
/// }
/// }
/// }
///
/// impl Events for Self {
/// type Data = ();
///
/// fn configure(&mut self, cx: &mut ConfigCx) {
/// if let Some(key) = self.label.accel_key() {
/// cx.add_accel_key(self.id_ref(), key.clone());
/// }
/// }
///
/// fn handle_event(&mut self, cx: &mut EventCx, _: &Self::Data, event: Event) -> Response {
/// fn handle_event(&mut self, cx: &mut EventCx, _: &(), event: Event) -> Response {
/// event.on_activate(cx, self.id(), |cx| {
/// cx.push(self.message.clone());
/// Response::Used
/// })
/// }
///
/// fn handle_messages(&mut self, cx: &mut EventCx, _: &()) {
/// if let Some(kas::message::Activate(code)) = cx.try_pop() {
/// cx.push(self.message.clone());
/// if let Some(code) = code {
/// cx.depress_with_key(self.id(), code);
/// }
/// }
/// }
/// }
/// }
/// ```
Expand Down Expand Up @@ -451,6 +489,8 @@ pub trait Widget: Layout {
fn _replay(&mut self, cx: &mut EventCx, data: &Self::Data, id: WidgetId, msg: Erased);

/// Internal method: search for the previous/next navigation target
///
/// `focus`: the current focus or starting point.
#[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
#[cfg_attr(doc_cfg, doc(cfg(internal_doc)))]
fn _nav_next(
Expand Down
24 changes: 11 additions & 13 deletions crates/kas-core/src/core/widget_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,15 @@ impl WidgetId {
}
}

/// Get the path len (number of indices, not storage length)
pub fn path_len(&self) -> usize {
match self.0.get() {
Variant::Invalid => panic!("WidgetId::path_len: invalid"),
Variant::Int(x) => BitsIter::new(x).count(),
Variant::Slice(path) => path.len(),
}
}

/// Returns true if `self` equals `id` or if `id` is a descendant of `self`
pub fn is_ancestor_of(&self, id: &Self) -> bool {
match (self.0.get(), id.0.get()) {
Expand Down Expand Up @@ -525,25 +534,14 @@ impl WidgetId {
pub unsafe fn opt_from_u64(n: u64) -> Option<WidgetId> {
IntOrPtr::opt_from_u64(n).map(WidgetId)
}

/// Construct an iterator, returning indices
///
/// This represents the widget's "path" from the root (window).
pub fn iter_path(&self) -> impl Iterator<Item = usize> + '_ {
match self.0.get() {
Variant::Invalid => panic!("WidgetId::iter_path on invalid"),
Variant::Int(x) => PathIter::Bits(BitsIter::new(x)),
Variant::Slice(path) => PathIter::Slice(path.iter().cloned()),
}
}
}

impl PartialEq for WidgetId {
fn eq(&self, rhs: &Self) -> bool {
match (self.0.get(), rhs.0.get()) {
(Variant::Invalid, _) | (_, Variant::Invalid) => panic!("WidgetId::eq: invalid id"),
(Variant::Int(x), Variant::Int(y)) => x == y,
_ => self.iter_path().eq(rhs.iter_path()),
_ => self.iter().eq(rhs.iter()),
}
}
}
Expand All @@ -560,7 +558,7 @@ impl Ord for WidgetId {
match (self.0.get(), rhs.0.get()) {
(Variant::Invalid, _) | (_, Variant::Invalid) => panic!("WidgetId::cmp: invalid id"),
(Variant::Int(x), Variant::Int(y)) => x.cmp(&y),
_ => self.iter_path().cmp(rhs.iter_path()),
_ => self.iter().cmp(rhs.iter()),
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/kas-core/src/event/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ impl ScrollComponent {
) -> (bool, Response) {
let mut moved = false;
match event {
Event::Command(cmd) => {
Event::Command(cmd, _) => {
let offset = match cmd {
Command::Home => Offset::ZERO,
Command::End => self.max_offset,
Expand Down
Loading

0 comments on commit e449c6a

Please sign in to comment.