Skip to content

Commit

Permalink
Merge pull request #421 from kas-gui/work
Browse files Browse the repository at this point in the history
Rename timer_update → timer, add AdaptEventCx, pass only ConfigCx to Widget::_nav_next
  • Loading branch information
dhardy authored Dec 4, 2023
2 parents c86b506 + f132619 commit 90b19d6
Show file tree
Hide file tree
Showing 22 changed files with 348 additions and 197 deletions.
6 changes: 3 additions & 3 deletions crates/kas-core/src/core/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

//! Widget method implementations

use crate::event::{Event, EventCx, FocusSource, IsUsed, Scroll, Unused, Used};
use crate::event::{ConfigCx, Event, EventCx, FocusSource, IsUsed, Scroll, Unused, Used};
#[cfg(debug_assertions)] use crate::util::IdentifyWidget;
use crate::{Erased, Events, Id, Layout, NavAdvance, Node, Widget};

Expand Down Expand Up @@ -132,7 +132,7 @@ pub fn _replay<W: Events>(
/// Generic implementation of [`Widget::_nav_next`]
pub fn _nav_next<W: Events>(
widget: &mut W,
cx: &mut EventCx,
cx: &mut ConfigCx,
data: &<W as Widget>::Data,
focus: Option<&Id>,
advance: NavAdvance,
Expand All @@ -143,7 +143,7 @@ pub fn _nav_next<W: Events>(

fn nav_next(
mut widget: Node<'_>,
cx: &mut EventCx,
cx: &mut ConfigCx,
focus: Option<&Id>,
advance: NavAdvance,
navigable: bool,
Expand Down
6 changes: 3 additions & 3 deletions crates/kas-core/src/core/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ trait NodeT {
fn _replay(&mut self, cx: &mut EventCx, id: Id, msg: Erased);
fn _nav_next(
&mut self,
cx: &mut EventCx,
cx: &mut ConfigCx,
focus: Option<&Id>,
advance: NavAdvance,
) -> Option<Id>;
Expand Down Expand Up @@ -102,7 +102,7 @@ impl<'a, T> NodeT for (&'a mut dyn Widget<Data = T>, &'a T) {
}
fn _nav_next(
&mut self,
cx: &mut EventCx,
cx: &mut ConfigCx,
focus: Option<&Id>,
advance: NavAdvance,
) -> Option<Id> {
Expand Down Expand Up @@ -369,7 +369,7 @@ impl<'a> Node<'a> {
// NOTE: public on account of ListView
pub fn _nav_next(
&mut self,
cx: &mut EventCx,
cx: &mut ConfigCx,
focus: Option<&Id>,
advance: NavAdvance,
) -> Option<Id> {
Expand Down
12 changes: 11 additions & 1 deletion crates/kas-core/src/core/widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,11 +383,15 @@ pub trait Widget: Layout {
}

/// Internal method: configure recursively
///
/// Do not implement this method directly!
#[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
#[cfg_attr(doc_cfg, doc(cfg(internal_doc)))]
fn _configure(&mut self, cx: &mut ConfigCx, data: &Self::Data, id: Id);

/// Internal method: update recursively
///
/// Do not implement this method directly!
#[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
#[cfg_attr(doc_cfg, doc(cfg(internal_doc)))]
fn _update(&mut self, cx: &mut ConfigCx, data: &Self::Data);
Expand All @@ -397,6 +401,8 @@ pub trait Widget: Layout {
/// If `disabled`, widget `id` does not receive the `event`. Widget `id` is
/// the first disabled widget (may be an ancestor of the original target);
/// ancestors of `id` are not disabled.
///
/// Do not implement this method directly!
#[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
#[cfg_attr(doc_cfg, doc(cfg(internal_doc)))]
fn _send(
Expand All @@ -412,18 +418,22 @@ pub trait Widget: Layout {
///
/// Behaves as if an event had been sent to `id`, then the widget had pushed
/// `msg` to the message stack. Widget `id` or any ancestor may handle.
///
/// Do not implement this method directly!
#[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
#[cfg_attr(doc_cfg, doc(cfg(internal_doc)))]
fn _replay(&mut self, cx: &mut EventCx, data: &Self::Data, id: Id, msg: Erased);

/// Internal method: search for the previous/next navigation target
///
/// `focus`: the current focus or starting point.
///
/// Do not implement this method directly!
#[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
#[cfg_attr(doc_cfg, doc(cfg(internal_doc)))]
fn _nav_next(
&mut self,
cx: &mut EventCx,
cx: &mut ConfigCx,
data: &Self::Data,
focus: Option<&Id>,
advance: NavAdvance,
Expand Down
59 changes: 32 additions & 27 deletions crates/kas-core/src/event/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ use crate::{Action, Id};
use kas_macros::impl_default;
use std::time::{Duration, Instant};

const PAYLOAD_SELECT: u64 = 1 << 60;
const PAYLOAD_GLIDE: u64 = (1 << 60) + 1;
const TIMER_SELECT: u64 = 1 << 60;
const TIMER_GLIDE: u64 = (1 << 60) + 1;
const GLIDE_POLL_MS: u64 = 3;
const GLIDE_MAX_SAMPLES: usize = 8;

Expand Down Expand Up @@ -198,13 +198,23 @@ impl ScrollComponent {
///
/// Returns [`Action::REGION_MOVED`] when the scroll offset changes.
pub fn focus_rect(&mut self, cx: &mut EventCx, rect: Rect, window_rect: Rect) -> Action {
let action = self.self_focus_rect(rect, window_rect);
cx.set_scroll(Scroll::Rect(rect - self.offset));
action
}

/// Scroll self to make the given `rect` visible
///
/// This is identical to [`Self::focus_rect`] except that it does not call
/// [`EventCx::set_scroll`], thus will not affect ancestors.
#[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
#[cfg_attr(doc_cfg, doc(cfg(internal_doc)))]
pub fn self_focus_rect(&mut self, rect: Rect, window_rect: Rect) -> Action {
self.glide.stop();
let v = rect.pos - window_rect.pos;
let off = Offset::conv(rect.size) - Offset::conv(window_rect.size);
let offset = self.offset.max(v + off).min(v);
let action = self.set_offset(offset);
cx.set_scroll(Scroll::Rect(rect - self.offset));
action
self.set_offset(offset)
}

/// Handle a [`Scroll`] action
Expand Down Expand Up @@ -249,7 +259,7 @@ impl ScrollComponent {
/// Use an event to scroll, if possible
///
/// Consumes the following events: `Command`, `Scroll`, `PressStart`,
/// `PressMove`, `PressEnd`, `TimerUpdate(pl)` where `pl == (1<<60) + 1`.
/// `PressMove`, `PressEnd`, `Timer(pl)` where `pl == (1<<60) + 1`.
/// May request timer updates.
///
/// Implements scroll by Home/End, Page Up/Down and arrow keys, by mouse
Expand Down Expand Up @@ -326,10 +336,10 @@ impl ScrollComponent {
let timeout = cx.config().scroll_flick_timeout();
let pan_dist_thresh = cx.config().pan_dist_thresh();
if self.glide.press_end(timeout, pan_dist_thresh) {
cx.request_timer_update(id.clone(), PAYLOAD_GLIDE, Duration::new(0, 0), true);
cx.request_timer(id.clone(), TIMER_GLIDE, Duration::new(0, 0));
}
}
Event::TimerUpdate(pl) if pl == PAYLOAD_GLIDE => {
Event::Timer(pl) if pl == TIMER_GLIDE => {
// Momentum/glide scrolling: update per arbitrary step time until movment stops.
let timeout = cx.config().scroll_flick_timeout();
let decay = cx.config().scroll_flick_decay();
Expand All @@ -338,7 +348,7 @@ impl ScrollComponent {

if self.glide.vel != Vec2::ZERO {
let dur = Duration::from_millis(GLIDE_POLL_MS);
cx.request_timer_update(id.clone(), PAYLOAD_GLIDE, dur, true);
cx.request_timer(id.clone(), TIMER_GLIDE, dur);
cx.set_scroll(Scroll::Scrolled);
}
}
Expand Down Expand Up @@ -399,7 +409,7 @@ impl TextInput {
/// Handle input events
///
/// Consumes the following events: `PressStart`, `PressMove`, `PressEnd`,
/// `TimerUpdate(pl)` where `pl == 1<<60 || pl == (1<<60)+1`.
/// `Timer(pl)` where `pl == 1<<60 || pl == (1<<60)+1`.
/// May request press grabs and timer updates.
///
/// Implements scrolling and text selection behaviour, excluding handling of
Expand All @@ -416,7 +426,7 @@ impl TextInput {
PressSource::Touch(touch_id) => {
self.touch_phase = TouchPhase::Start(touch_id, press.coord);
let delay = cx.config().touch_select_delay();
cx.request_timer_update(w_id.clone(), PAYLOAD_SELECT, delay, false);
cx.request_timer(w_id.clone(), TIMER_SELECT, delay);
None
}
PressSource::Mouse(..) if cx.config_enable_mouse_text_pan() => {
Expand Down Expand Up @@ -474,32 +484,27 @@ impl TextInput {
|| matches!(press.source, PressSource::Mouse(..) if cx.config_enable_mouse_text_pan()))
{
self.touch_phase = TouchPhase::None;
cx.request_timer_update(w_id, PAYLOAD_GLIDE, Duration::new(0, 0), true);
cx.request_timer(w_id, TIMER_GLIDE, Duration::new(0, 0));
}
Action::None
}
Event::TimerUpdate(pl) if pl == PAYLOAD_SELECT => {
match self.touch_phase {
TouchPhase::Start(touch_id, coord) => {
self.touch_phase = TouchPhase::Cursor(touch_id);
Action::Focus {
coord: Some(coord),
action: SelectionAction::new(true, !cx.modifiers().shift_key(), 1),
}
Event::Timer(pl) if pl == TIMER_SELECT => match self.touch_phase {
TouchPhase::Start(touch_id, coord) => {
self.touch_phase = TouchPhase::Cursor(touch_id);
Action::Focus {
coord: Some(coord),
action: SelectionAction::new(true, !cx.modifiers().shift_key(), 1),
}
// Note: if the TimerUpdate were from another requester it
// should technically be Unused, but it doesn't matter
// so long as other consumers match this first.
_ => Action::None,
}
}
Event::TimerUpdate(pl) if pl == PAYLOAD_GLIDE => {
_ => Action::None,
},
Event::Timer(pl) if pl == TIMER_GLIDE => {
// Momentum/glide scrolling: update per arbitrary step time until movment stops.
let timeout = cx.config().scroll_flick_timeout();
let decay = cx.config().scroll_flick_decay();
if let Some(delta) = self.glide.step(timeout, decay) {
let dur = Duration::from_millis(GLIDE_POLL_MS);
cx.request_timer_update(w_id, PAYLOAD_GLIDE, dur, true);
cx.request_timer(w_id, TIMER_GLIDE, dur);
Action::Pan(delta)
} else {
Action::None
Expand Down
22 changes: 11 additions & 11 deletions crates/kas-core/src/event/cx/cx_pub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,35 +188,35 @@ impl EventState {
}
}

/// Schedule an update
/// Schedule a timed update
///
/// Widget updates may be used for animation and timed responses. See also
/// [`Draw::animate`](crate::draw::Draw::animate) for animation.
///
/// Widget `id` will receive [`Event::TimerUpdate`] with this `payload` at
/// Widget `id` will receive [`Event::Timer`] with this `payload` at
/// approximately `time = now + delay` (or possibly a little later due to
/// frame-rate limiters and processing time).
///
/// Requesting an update with `delay == 0` is valid, except from an
/// [`Event::TimerUpdate`] handler (where it may cause an infinite loop).
/// [`Event::Timer`] handler (where it may cause an infinite loop).
///
/// If multiple updates with the same `id` and `payload` are requested,
/// these are merged (using the earliest time if `first` is true).
pub fn request_timer_update(&mut self, id: Id, payload: u64, delay: Duration, first: bool) {
/// Multiple timer requests with the same `id` and `payload` are merged
/// (choosing the earliest time).
pub fn request_timer(&mut self, id: Id, payload: u64, delay: Duration) {
let time = Instant::now() + delay;
if let Some(row) = self
.time_updates
.iter_mut()
.find(|row| row.1 == id && row.2 == payload)
{
if (first && row.0 <= time) || (!first && row.0 >= time) {
if row.0 <= time {
return;
}

row.0 = time;
log::trace!(
target: "kas_core::event",
"request_timer_update: update {id} at now+{}ms",
"request_timer: update {id} at now+{}ms",
delay.as_millis()
);
} else {
Expand Down Expand Up @@ -399,7 +399,7 @@ impl EventState {
#[inline]
pub fn request_key_focus(&mut self, target: Id, source: FocusSource) {
self.pending_sel_focus = Some(PendingSelFocus {
target,
target: Some(target),
key_focus: true,
source,
});
Expand All @@ -421,13 +421,13 @@ impl EventState {
#[inline]
pub fn request_sel_focus(&mut self, target: Id, source: FocusSource) {
if let Some(ref pending) = self.pending_sel_focus {
if pending.target == target {
if pending.target.as_ref() == Some(&target) {
return;
}
}

self.pending_sel_focus = Some(PendingSelFocus {
target,
target: Some(target),
key_focus: false,
source,
});
Expand Down
Loading

0 comments on commit 90b19d6

Please sign in to comment.