Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Less type safety: remove Handler::Msg assoc. type #309

Merged
merged 47 commits into from
Apr 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
f2bc440
EventMgr: add variadic message queue
dhardy Apr 19, 2022
ae6a8e4
Remove "Received by ..." message route tracing
dhardy Apr 19, 2022
c7a6ca0
ComboBox, SubMenu: do not handle Response::Select
dhardy Apr 20, 2022
1b315f8
Remove #[widget(update = f)]
dhardy Apr 21, 2022
9cc2861
Remove Response::Update
dhardy Apr 21, 2022
08768da
Remove Handler::Msg type parameter
dhardy Apr 22, 2022
4713928
Remove IndexedList, IndexedColumn, IndexedRow, GenericList types
dhardy Apr 22, 2022
15f7956
Remove #[widget{ msg = T; }]
dhardy Apr 22, 2022
1bdaabc
Simplify examples/custom-theme.rs
dhardy Apr 22, 2022
4a025e3
Replace Response::Select with SelectMsg
dhardy Apr 22, 2022
e81bdbb
Replace Response::Scrolled, Pan, Focus
dhardy Apr 22, 2022
8d16fca
Replace dyn WidgetConfig with dyn Widget
dhardy Apr 22, 2022
ca1f392
Remove Response result from on_message
dhardy Apr 22, 2022
1e25789
Remove SendEvent trait; new EventMgr::send method
dhardy Apr 23, 2022
f66b3b0
ComboBox: push accelerator keys to a new layer
dhardy Apr 23, 2022
c726445
Fix stopwatch: use messages in buttons
dhardy Apr 23, 2022
e7eebc8
Mandlebrot: enable tab focus of main widget
dhardy Apr 23, 2022
d57ed01
Splitter: fix child index / WidgetId assignment
dhardy Apr 23, 2022
8c230ad
ComboBox: fix highlight of entry when opened
dhardy Apr 23, 2022
c98d1ab
Require message types implement Debug
dhardy Apr 23, 2022
3ce9b22
Print value of unhandled messages (debug only)
dhardy Apr 23, 2022
5427d1f
EventMgr: unify send_event and try_send_event
dhardy Apr 23, 2022
76143be
Fix widget derive
dhardy Apr 24, 2022
320d46c
DragHandle: return the raw offset without updating
dhardy Apr 24, 2022
24fdb92
Add kas::event::MsgPressFocus
dhardy Apr 24, 2022
d109a38
Fix: EditField and RadioBox closures do not return Option<M>
dhardy Apr 24, 2022
5457e21
ComboBox: remove 'active' parameter from constructor
dhardy Apr 25, 2022
975a410
ComboBox: add an associated message to each entry
dhardy Apr 25, 2022
423d77c
Rename Handler::handle → handle_event
dhardy Apr 25, 2022
37cec51
Rename Handler::on_message → handle_message
dhardy Apr 25, 2022
773fad2
Add List::on_message, Grid::on_message; remove IndexMsg
dhardy Apr 25, 2022
16a8ca4
Gallery: fix "Edit" button
dhardy Apr 25, 2022
f885fff
Remove Driver::get (unused)
dhardy Apr 25, 2022
1fb1158
Replace *Data::update_handles with update_on_handles
dhardy Apr 25, 2022
29d0029
*Data::update, handle_message: trigger update directly instead of ret…
dhardy Apr 25, 2022
eab5f3b
Fix examples/filter-list.rs: updating of filter
dhardy Apr 25, 2022
9c97461
examples/data-list-view.rs: correct initial display contents
dhardy Apr 25, 2022
c6156ef
Prettify WidgetHeirarchy output
dhardy Apr 25, 2022
3f995ee
Fix layout bad width/height interaction
dhardy Apr 25, 2022
80d99e1
Fix examples/data-list.rs: message handling
dhardy Apr 25, 2022
92a3185
Rename Handler::scroll → handle_scroll
dhardy Apr 25, 2022
d22e412
Clippy fixes
dhardy Apr 25, 2022
81116e3
Fix doc links
dhardy Apr 26, 2022
ec2cb54
Response is no longer #[must_use]
dhardy Apr 26, 2022
f0d77e8
Fix test: combobox
dhardy Apr 26, 2022
f921dd2
Bump MSRV to 1.58
dhardy Apr 26, 2022
149e34f
Fix examples/mandlebrot
dhardy Apr 26, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ jobs:
toolchain: [stable]
include:
- os: ubuntu-latest
toolchain: "1.56.0"
toolchain: "1.58.0"
- os: ubuntu-latest
toolchain: beta

Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ keywords = ["gui"]
categories = ["gui"]
repository = "https://github.com/kas-gui/kas"
exclude = ["/examples"]
rust-version = "1.56"
rust-version = "1.58"

[package.metadata.docs.rs]
features = ["nightly"]
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ Getting started

### Dependencies

KAS requires a [Rust] compiler, version (MSRV) 1.56 or greater.
KAS requires a [Rust] compiler, version (MSRV) 1.58 or greater.
Using the **nightly** channel does have a few advantages:

- Procedural macros can only emit warnings using nightly `rustc`.
Expand Down
6 changes: 3 additions & 3 deletions crates/kas-core/src/core/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
#[allow(unused)]
use super::Layout;
use super::{Widget, WidgetId};
use crate::event::{self, EventMgr};
use crate::event::EventMgr;
use crate::geom::Rect;
use crate::layout::{SetRectMgr, StorageChain};
use crate::{dir::Direction, WindowId};
Expand Down Expand Up @@ -74,7 +74,7 @@ impl Clone for CoreData {
/// visible). The window is responsible for calling these methods.
//
// NOTE: it's tempting to include a pointer to the widget here. There are two
// options: (a) an unsafe aliased pointer or (b) Rc<RefCell<dyn WidgetConfig>>.
// options: (a) an unsafe aliased pointer or (b) Rc<RefCell<dyn Widget>>.
// Option (a) should work but is an unnecessary performance hack; (b) could in
// theory work but requires adjusting WidgetChildren::get, find etc. to take a
// closure instead of returning a reference, causing *significant* complication.
Expand All @@ -86,7 +86,7 @@ pub struct Popup {
}

/// Functionality required by a window
pub trait Window: Widget<Msg = event::VoidMsg> {
pub trait Window: Widget {
/// Get the window title
fn title(&self) -> &str;

Expand Down
4 changes: 2 additions & 2 deletions crates/kas-core/src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ pub trait Boxed<T: ?Sized> {
fn boxed(self) -> Box<T>;
}

impl<W: Widget + Sized> Boxed<dyn Widget<Msg = W::Msg>> for W {
fn boxed(self) -> Box<dyn Widget<Msg = W::Msg>> {
impl<W: Widget + Sized> Boxed<dyn Widget> for W {
fn boxed(self) -> Box<dyn Widget> {
Box::new(self)
}
}
24 changes: 10 additions & 14 deletions crates/kas-core/src/core/widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ pub trait WidgetCore: Any + fmt::Debug {
fn widget_name(&self) -> &'static str;

/// Erase type
fn as_widget(&self) -> &dyn WidgetConfig;
fn as_widget(&self) -> &dyn Widget;
/// Erase type
fn as_widget_mut(&mut self) -> &mut dyn WidgetConfig;
fn as_widget_mut(&mut self) -> &mut dyn Widget;
}

/// Listing of a widget's children
Expand Down Expand Up @@ -100,15 +100,15 @@ pub trait WidgetChildren: WidgetCore {
/// For convenience, `Index<usize>` is implemented via this method.
///
/// Required: `index < self.len()`.
fn get_child(&self, index: usize) -> Option<&dyn WidgetConfig>;
fn get_child(&self, index: usize) -> Option<&dyn Widget>;

/// 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 [`EventState::send_action`].
/// This method may be removed in the future.
fn get_child_mut(&mut self, index: usize) -> Option<&mut dyn WidgetConfig>;
fn get_child_mut(&mut self, index: usize) -> Option<&mut dyn Widget>;

/// Find the child which is an ancestor of this `id`, if any
///
Expand Down Expand Up @@ -401,9 +401,7 @@ pub trait Layout: WidgetChildren {
/// - [`Layout`] — handles sizing and positioning of self and children
/// - [`WidgetConfig`] — the last unparametrised trait allows customisation of
/// some aspects of widget behaviour
/// - [`event::Handler`] — parametrised widgets over a `Msg` type and handles
/// events
/// - [`event::SendEvent`] — routes events to children and handles responses
/// - [`event::Handler`] — handles events
/// - [`Widget`] — the final trait
///
/// Widgets **must** use the [`derive(Widget)`] macro to implement at least
Expand All @@ -413,14 +411,12 @@ pub trait Layout: WidgetChildren {
/// implement *all except for `Layout`*. This opt-out derive behaviour means
/// that adding additional traits into the family is not a breaking change.
///
/// To refer to a widget via dyn trait, use `&dyn WidgetConfig` (or, if the
/// message type is known, one may use `&dyn Widget<Msg = M>`).
/// To refer to a widget in generic functions, use `<W: Widget>` or
/// `<M, W: Widget<Msg = M>>`.
/// To refer to a widget via dyn trait, use `&dyn Widget`.
/// To refer to a widget in generic functions, use `<W: Widget>`.
///
/// [`derive(Widget)`]: https://docs.rs/kas/latest/kas/macros/index.html#the-derivewidget-macro
#[autoimpl(for<T: trait + ?Sized> Box<T>)]
pub trait Widget: event::SendEvent {}
pub trait Widget: event::Handler {}

/// Extension trait over widgets
pub trait WidgetExt: WidgetChildren {
Expand Down Expand Up @@ -488,7 +484,7 @@ pub trait WidgetExt: WidgetChildren {
}

/// Find the descendant with this `id`, if any
fn find_widget(&self, id: &WidgetId) -> Option<&dyn WidgetConfig> {
fn find_widget(&self, id: &WidgetId) -> Option<&dyn Widget> {
if let Some(index) = self.find_child_index(id) {
self.get_child(index)
.and_then(|child| child.find_widget(id))
Expand All @@ -500,7 +496,7 @@ pub trait WidgetExt: WidgetChildren {
}

/// Find the descendant with this `id`, if any
fn find_widget_mut(&mut self, id: &WidgetId) -> Option<&mut dyn WidgetConfig> {
fn find_widget_mut(&mut self, id: &WidgetId) -> Option<&mut dyn Widget> {
if let Some(index) = self.find_child_index(id) {
self.get_child_mut(index)
.and_then(|child| child.find_widget_mut(id))
Expand Down
136 changes: 57 additions & 79 deletions crates/kas-core/src/event/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
//! Event handling components

use super::ScrollDelta::{LineDelta, PixelDelta};
use super::{Command, CursorIcon, Event, EventMgr, PressSource, Response, VoidMsg};
use super::{Command, CursorIcon, Event, EventMgr, PressSource, Response, Scroll};
use crate::cast::traits::*;
use crate::geom::{Coord, Offset, Rect, Size, Vec2};
#[allow(unused)]
Expand Down Expand Up @@ -151,7 +151,6 @@ impl ScrollComponent {
/// The offset is clamped to the available scroll range.
/// Returns [`TkAction::empty()`] if the offset is identical to the old offset,
/// or [`TkAction::REGION_MOVED`] if the offset changes.
#[inline]
pub fn set_offset(&mut self, offset: Offset) -> TkAction {
let offset = offset.min(self.max_offset).max(Offset::ZERO);
if offset == self.offset {
Expand All @@ -162,34 +161,15 @@ impl ScrollComponent {
}
}

/// Apply offset to an event being sent to the scrolled child
#[inline]
pub fn offset_event(&self, mut event: Event) -> Event {
match &mut event {
Event::PressStart { coord, .. } => {
*coord += self.offset;
}
Event::PressMove { coord, .. } => {
*coord += self.offset;
}
Event::PressEnd { coord, .. } => {
*coord += self.offset;
}
_ => {}
};
event
}

/// Handle [`Response::Focus`]
/// Scroll to make the given `rect` visible
///
/// Inputs and outputs:
///
/// - `rect`: the focus rect
/// - `rect`: the rect to focus
/// - `window_rect`: the rect of the scroll window
/// - returned `Rect`: the focus rect, adjusted for scroll offset; normally this should be
/// returned via another [`Response::Focus`]
/// - returned `Rect`: the focus rect, adjusted for scroll offset; this
/// may be set via [`EventMgr::set_scroll`]
/// - returned `TkAction`: action to pass to the event manager
#[inline]
pub fn focus_rect(&mut self, rect: Rect, window_rect: Rect) -> (Rect, TkAction) {
let v = rect.pos - window_rect.pos;
let off = Offset::conv(rect.size) - Offset::conv(window_rect.size);
Expand All @@ -198,6 +178,26 @@ impl ScrollComponent {
(rect - self.offset, action)
}

/// Handle a [`Scroll`] action
pub fn scroll(&mut self, mgr: &mut EventMgr, window_rect: Rect, scroll: Scroll) -> Scroll {
match scroll {
s @ Scroll::None | s @ Scroll::Scrolled => s,
Scroll::Offset(delta) => {
let old_offset = self.offset;
*mgr |= self.set_offset(old_offset - delta);
match delta - old_offset + self.offset {
delta if delta == Offset::ZERO => Scroll::Scrolled,
delta => Scroll::Offset(delta),
}
}
Scroll::Rect(rect) => {
let (rect, action) = self.focus_rect(rect, window_rect);
*mgr |= action;
Scroll::Rect(rect)
}
}
}

/// Use an event to scroll, if possible
///
/// Consumes the following events: `Command`, `Scroll`, `PressStart`,
Expand All @@ -212,76 +212,53 @@ impl ScrollComponent {
/// depend on modifiers), and if so grabs press events from this `source`.
/// `PressMove` is used to scroll by the motion delta and to track speed;
/// `PressEnd` initiates momentum-scrolling if the speed is high enough.
///
/// If the returned [`TkAction`] is `None`, the scroll offset has not changed and
/// the returned [`Response`] is either `Used` or `Unused`.
/// If the returned [`TkAction`] is not `None`, the scroll offset has been
/// updated and the second return value is `Response::Used`.
#[inline]
pub fn scroll_by_event(
&mut self,
mgr: &mut EventMgr,
event: Event,
id: WidgetId,
window_size: Size,
) -> (TkAction, Response<VoidMsg>) {
let mut action = TkAction::empty();
let mut response = Response::Used;

window_rect: Rect,
) -> Response {
match event {
Event::Command(Command::Home, _) => {
action = self.set_offset(Offset::ZERO);
}
Event::Command(Command::End, _) => {
action = self.set_offset(self.max_offset);
}
Event::Command(cmd, _) => {
let delta = match cmd {
Command::Left => LineDelta(-1.0, 0.0),
Command::Right => LineDelta(1.0, 0.0),
Command::Up => LineDelta(0.0, 1.0),
Command::Down => LineDelta(0.0, -1.0),
Command::PageUp => PixelDelta(Offset(0, window_size.1 / 2)),
Command::PageDown => PixelDelta(Offset(0, -(window_size.1 / 2))),
_ => return (action, Response::Unused),
};

let d = match delta {
LineDelta(x, y) => mgr.config().scroll_distance((-x, y), None),
PixelDelta(d) => d,
let offset = match cmd {
Command::Home => Offset::ZERO,
Command::End => self.max_offset,
cmd => {
let delta = match cmd {
Command::Left => LineDelta(-1.0, 0.0),
Command::Right => LineDelta(1.0, 0.0),
Command::Up => LineDelta(0.0, 1.0),
Command::Down => LineDelta(0.0, -1.0),
Command::PageUp => PixelDelta(Offset(0, window_rect.size.1 / 2)),
Command::PageDown => PixelDelta(Offset(0, -(window_rect.size.1 / 2))),
_ => return Response::Unused,
};
let delta = match delta {
LineDelta(x, y) => mgr.config().scroll_distance((-x, y), None),
PixelDelta(d) => d,
};
self.offset - delta
}
};
action = self.set_offset(self.offset - d);
*mgr |= self.set_offset(offset);
mgr.set_scroll(Scroll::Rect(window_rect));
}
Event::Scroll(delta) => {
let d = match delta {
mgr.set_scroll(Scroll::Offset(match delta {
LineDelta(x, y) => mgr.config().scroll_distance((-x, y), None),
PixelDelta(d) => d,
};
let old_offset = self.offset;
action = self.set_offset(old_offset - d);
let delta = d - (old_offset - self.offset);
response = if delta != Offset::ZERO {
Response::Pan(delta)
} else {
Response::Scrolled
};
}));
}
Event::PressStart { source, coord, .. }
if self.max_offset != Offset::ZERO && mgr.config_enable_pan(source) =>
{
let icon = Some(CursorIcon::Grabbing);
mgr.grab_press_unique(id, source, coord, icon);
}
Event::PressMove { mut delta, .. } => {
Event::PressMove { delta, .. } => {
self.glide.move_delta(delta);
let old_offset = self.offset;
action = self.set_offset(old_offset - delta);
delta = old_offset - self.offset;
response = if delta != Offset::ZERO {
Response::Pan(delta)
} else {
Response::Scrolled
};
mgr.set_scroll(Scroll::Offset(delta));
}
Event::PressEnd { .. } => {
if self.glide.opt_start(mgr.config().scroll_flick_timeout()) {
Expand All @@ -292,20 +269,21 @@ impl ScrollComponent {
// Momentum/glide scrolling: update per arbitrary step time until movment stops.
let decay = mgr.config().scroll_flick_decay();
if let Some(delta) = self.glide.step(decay) {
action = self.set_offset(self.offset - delta);
let action = self.set_offset(self.offset - delta);
*mgr |= action;
if delta == Offset::ZERO || !action.is_empty() {
// Note: when FPS > pixels/sec, delta may be zero while
// still scrolling. Glide returns None when we're done,
// but we're also done if unable to scroll further.
let dur = Duration::from_millis(GLIDE_POLL_MS);
mgr.update_on_timer(dur, id, PAYLOAD_GLIDE);
response = Response::Scrolled;
mgr.set_scroll(Scroll::Scrolled);
}
}
}
_ => response = Response::Unused,
_ => return Response::Unused,
}
(action, response)
Response::Used
}
}

Expand Down
Loading