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

Refactor/input #684

Merged
merged 5 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,18 @@ Please only add new entries below the [Unreleased](#unreleased---releasedate) he
### Features

- **core**: ensure object safety of `StateReader`, `StateWatcher` and `StateWriter` (#692 @M-Adoo)
- **core**: Support extend custom event. (#684 @wjian23)
- **core**: Added `map_watcher` to `StateWatcher` (#684 @wjian23)
- **core**: Added `ensure_visible` and ScrollableProvider to ScrollableWidget, to support descendant to be showed.(#684 @wjian23)

### Changed

- **core**: Unified implementation of IntoWidget for impl StateWriter<V:Compose>. (#684 @wjian23)
- **widgets**: Refactor `Input` Widget. (#684 @wjian23)

### Breading

- **core**: Rename `can_focus` field of FocusScope to `skip_host`. (#684 @wjian23)

## [0.4.0-alpha.23] - 2025-01-15

Expand Down Expand Up @@ -66,6 +78,7 @@ Please only add new entries below the [Unreleased](#unreleased---releasedate) he

- **core**: The animation finish may miss drawing the last frame. (#682 @M-Adoo)


## [0.4.0-alpha.20] - 2024-12-25

### Features
Expand Down
14 changes: 14 additions & 0 deletions core/src/builtin_widgets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,20 @@ impl<T> FatObj<T> {
on_mixin!(self, on_focus_out, f)
}

/// Attaches a handler to the specific custom event that is bubbled from the
/// descendants.
pub fn on_custom_concrete_event<E: 'static>(
mut self, f: impl FnMut(&mut CustomEvent<E>) + 'static,
) -> Self {
on_mixin!(self, on_custom_concrete_event, f)
}

/// Attaches a handler to raw custom event that is bubbled from the
/// descendants.
pub fn on_custom_event(mut self, f: impl FnMut(&mut RawCustomEvent) + 'static) -> Self {
on_mixin!(self, on_custom_event, f)
}

/// Attaches a handler to the widget that is triggered during the capture
/// phase of a focus out event. This is similar to `on_focus_out`, but it's
/// triggered earlier in the event flow. For more information on event
Expand Down
14 changes: 7 additions & 7 deletions core/src/builtin_widgets/focus_scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ pub struct FocusScope {
#[declare(default)]
pub skip_descendants: bool,

/// If true, the child widget can be focused.
/// Default value is false, then the child widget can't be focused, but not
/// skip the whole subtree.
#[declare(default)]
pub can_focus: bool,
/// If true (default), then the host widget can not be focused, but not skip
/// the whole subtree if skip_descendants is false.
/// If false, then the host widget can be focused.
#[declare(default = true)]
pub skip_host: bool,
}

impl<'c> ComposeChild<'c> for FocusScope {
Expand Down Expand Up @@ -108,7 +108,7 @@ mod tests {
@MockMulti {
@MockBox { size, tab_index: 0i16, auto_focus: true }
@FocusScope {
can_focus: true,
skip_host: false,
skip_descendants: true,
tab_index: 3i16,
@MockMulti {
Expand Down Expand Up @@ -156,7 +156,7 @@ mod tests {
let result = tap_cnt.clone_reader();
let widget = fn_widget! {
let mut host = @FocusScope {
can_focus: false,
skip_host: true,
on_key_down: move |_| *$tap_cnt.write() += 1,
};
let request_focus_box = @MockBox {
Expand Down
26 changes: 23 additions & 3 deletions core/src/builtin_widgets/mix_builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,17 @@ bitflags! {
FocusIn/FocusOut and their capture events"]
const FocusInOut = 1 << 5;

#[doc="Bubble custom event listener flag, hint the widget is listening to \
custom events"]
const Customs = 1 << 6;

const AllListeners = Self::Lifecycle.bits()
| Self::Pointer.bits()
| Self::Wheel.bits()
| Self::KeyBoard.bits()
| Self::Focus.bits()
| Self::FocusInOut.bits();
| Self::FocusInOut.bits()
| Self::Customs.bits();
// listener end

#[doc="Indicates whether this widget is tracing its focus status."]
Expand Down Expand Up @@ -67,7 +72,7 @@ impl Declare for MixBuiltin {
}

macro_rules! event_map_filter {
($event_name:ident, $event_ty:ident) => {
($event_name:ident, $event_ty:ty) => {
(|e| match e {
Event::$event_name(e) => Some(e),
_ => None,
Expand All @@ -76,7 +81,7 @@ macro_rules! event_map_filter {
}

macro_rules! impl_event_callback {
($this:ident, $listen_type:ident, $event_name:ident, $event_ty:ident, $handler:ident) => {{
($this:ident, $listen_type:ident, $event_name:ident, $event_ty:ty, $handler:ident) => {{
$this.silent_mark(MixFlags::$listen_type);
let _ = $this
.subject()
Expand Down Expand Up @@ -325,6 +330,21 @@ impl MixBuiltin {
impl_event_callback!(self, FocusInOut, FocusOutCapture, FocusEvent, f)
}

pub fn on_custom_concrete_event<E: 'static, F: FnMut(&mut CustomEvent<E>) + 'static>(
&self, mut f: F,
) -> &Self {
let wrap_f = move |arg: &mut RawCustomEvent| {
if let Some(e) = arg.downcast_mut::<E>() {
f(e);
}
};
impl_event_callback!(self, Customs, CustomEvent, RawCustomEvent, wrap_f)
}

pub fn on_custom_event(&self, f: impl FnMut(&mut RawCustomEvent) + 'static) -> &Self {
impl_event_callback!(self, Customs, CustomEvent, RawCustomEvent, f)
}

/// Begin tracing the focus status of this widget.
pub fn trace_focus(&self) {
if !self.contain_flag(MixFlags::TraceFocus) {
Expand Down
68 changes: 67 additions & 1 deletion core/src/builtin_widgets/scrollable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,14 @@ pub struct ScrollableWidget {
scroll_pos: Point,
page: Size,
content_size: Size,

view_id: Option<TrackId>,
}

/// the descendant widgets of `Scrollable` can use
/// `Provider::state_of::<ScrollableProvider>` to scroll the view.
pub type ScrollableProvider = Box<dyn StateWriter<Value = ScrollableWidget>>;

impl Declare for ScrollableWidget {
type Builder = FatObj<()>;
#[inline]
Expand Down Expand Up @@ -62,13 +68,73 @@ impl<'c> ComposeChild<'c> for ScrollableWidget {
.distinct_until_changed()
.subscribe(move |v| $this.write().set_page(v));

@Clip { @ $view { @ { child } } }
$this.write().view_id = Some($view.track_id());
@Clip {
providers: [Provider::value_of_writer(this.clone_boxed_writer(), None)],
@ $view { @ { child } }
}
}
.into_widget()
}
}

impl ScrollableWidget {
pub fn map_to_view(&self, p: Point, child: WidgetId, wnd: &Window) -> Option<Point> {
let view_id = self.view_id.as_ref()?.get()?;
let pos = wnd.map_to_global(p, child);
let base = wnd.map_to_global(Point::zero(), view_id);
Some(pos - base.to_vector())
}

/// Ensure the given child is visible in the scroll view with the given anchor
/// relative to the view.
/// If Anchor.x is None, it will anchor the widget to the closest edge of the
/// view in horizontal direction, when the widget is out of the view.
/// If Anchor.y is None, it will anchor the widget to the closest edge of the
/// view in vertical direction, when the widget is out of the view.
pub fn ensure_visible(&mut self, child: WidgetId, anchor: Anchor, wnd: &Window) {
let Some(pos) = self.map_to_view(Point::zero(), child, wnd) else { return };
let Some(size) = wnd.widget_size(child) else { return };
let child_rect = Rect::new(pos, size);
let view_size = self.scroll_view_size();

let best_auto_position_fn = |min: f32, max: f32, max_limit: f32| {
if (min < 0. && max_limit < max) || (0. < min && max < max_limit) {
min
} else if min.abs() < (max - max_limit).abs() {
0.
} else {
max_limit - (max - min)
}
};
let Anchor { x, y } = anchor;
let top_left = match (x, y) {
(Some(x), Some(y)) => Point::new(
x.into_pixel(child_rect.width(), view_size.width),
y.into_pixel(child_rect.height(), view_size.height),
),
(Some(x), None) => {
let best_y =
best_auto_position_fn(child_rect.min_y(), child_rect.max_y(), view_size.height);
Point::new(x.into_pixel(child_rect.width(), view_size.width), best_y)
}
(None, Some(y)) => {
let best_x = best_auto_position_fn(child_rect.min_x(), child_rect.max_x(), view_size.width);
Point::new(best_x, y.into_pixel(child_rect.height(), view_size.height))
}
(None, None) => {
let best_x = best_auto_position_fn(child_rect.min_x(), child_rect.max_x(), view_size.width);
let best_y =
best_auto_position_fn(child_rect.min_y(), child_rect.max_y(), view_size.height);
Point::new(best_x, best_y)
}
};

let old = self.get_scroll_pos();
let offset = child_rect.origin - top_left;
self.jump_to(old + offset);
}

pub fn scroll(&mut self, x: f32, y: f32) {
let mut new = self.scroll_pos;
if self.scrollable != Scrollable::X {
Expand Down
42 changes: 31 additions & 11 deletions core/src/builtin_widgets/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,36 @@ pub struct Text {
glyphs: RefCell<Option<VisualGlyphs>>,
}

pub fn text_glyph(
text: Substr, text_style: &TextStyle, text_align: TextAlign, bounds: Size,
) -> VisualGlyphs {
AppCtx::typography_store()
.borrow_mut()
.typography(
text,
text_style,
bounds,
text_align,
GlyphBaseline::Middle,
PlaceLineDirection::TopToBottom,
)
}

pub fn paint_text(
painter: &mut Painter, glyphs: &VisualGlyphs, style: PaintingStyle, box_rect: Rect,
) {
if let PaintingStyle::Stroke(options) = style {
painter
.set_style(PathStyle::Stroke)
.set_strokes(options);
} else {
painter.set_style(PathStyle::Fill);
}

let font_db = AppCtx::font_db().clone();
painter.draw_glyphs_in_rect(glyphs, box_rect, &font_db.borrow());
}

impl Render for Text {
fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size {
let style = Provider::of::<TextStyle>(ctx).unwrap();
Expand Down Expand Up @@ -50,18 +80,8 @@ impl Render for Text {
};

let style = Provider::of::<PaintingStyle>(ctx).map(|p| p.clone());
let painter = ctx.painter();
if let Some(PaintingStyle::Stroke(options)) = style {
painter
.set_style(PathStyle::Stroke)
.set_strokes(options);
} else {
painter.set_style(PathStyle::Fill);
}

let visual_glyphs = self.glyphs().unwrap();
let font_db = AppCtx::font_db().clone();
painter.draw_glyphs_in_rect(&visual_glyphs, box_rect, &font_db.borrow());
paint_text(ctx.painter(), &visual_glyphs, style.unwrap_or(PaintingStyle::Fill), box_rect);
}
}

Expand Down
2 changes: 1 addition & 1 deletion core/src/builtin_widgets/visibility.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ impl<'c> ComposeChild<'c> for Visibility {
fn_widget! {
@FocusScope {
skip_descendants: pipe!(!$this.get_visible()),
can_focus: pipe!($this.get_visible()),
skip_host: pipe!(!$this.get_visible()),
@VisibilityRender {
display: pipe!($this.get_visible()),
@ { child }
Expand Down
9 changes: 8 additions & 1 deletion core/src/events.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::ptr::NonNull;
use std::{any::Any, ptr::NonNull};

use self::dispatcher::DispatchInfo;
use crate::{
Expand All @@ -11,6 +11,8 @@ use crate::{

pub(crate) mod dispatcher;
pub use dispatcher::GrabPointer;
pub mod custom_event;
pub use custom_event::*;
mod pointers;
pub use pointers::*;
use ribir_geom::Point;
Expand Down Expand Up @@ -175,6 +177,8 @@ pub enum Event {
/// The main difference between this event and focusout is that focusout emit
/// in bubbles phase but this event emit in capture phase.
FocusOutCapture(FocusEvent),
/// Custom event.
CustomEvent(CustomEvent<dyn Any>),
}

impl std::ops::Deref for Event {
Expand Down Expand Up @@ -207,6 +211,7 @@ impl std::ops::Deref for Event {
Event::Wheel(e) | Event::WheelCapture(e) => e,
Event::Chars(e) | Event::CharsCapture(e) => e,
Event::KeyDown(e) | Event::KeyDownCapture(e) | Event::KeyUp(e) | Event::KeyUpCapture(e) => e,
Event::CustomEvent(e) => e,
}
}
}
Expand Down Expand Up @@ -239,6 +244,7 @@ impl std::ops::DerefMut for Event {
Event::Wheel(e) | Event::WheelCapture(e) => e,
Event::Chars(e) | Event::CharsCapture(e) => e,
Event::KeyDown(e) | Event::KeyDownCapture(e) | Event::KeyUp(e) | Event::KeyUpCapture(e) => e,
Event::CustomEvent(e) => e,
}
}
}
Expand Down Expand Up @@ -273,6 +279,7 @@ impl Event {
| Event::FocusInCapture(_)
| Event::FocusOut(_)
| Event::FocusOutCapture(_) => MixFlags::FocusInOut,
Event::CustomEvent(_) => MixFlags::Customs,
}
}
}
Expand Down
Loading
Loading