Skip to content

Commit

Permalink
Merge pull request #309 from kas-gui/work2
Browse files Browse the repository at this point in the history
Less type safety: remove Handler::Msg assoc. type
  • Loading branch information
dhardy committed Apr 26, 2022
2 parents 8de2d60 + 149e34f commit e32b1c1
Show file tree
Hide file tree
Showing 78 changed files with 1,987 additions and 2,976 deletions.
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

0 comments on commit e32b1c1

Please sign in to comment.