Skip to content
This repository has been archived by the owner on May 12, 2022. It is now read-only.

Commit

Permalink
feat(ui): add Entry[Core]::subscribe_changed
Browse files Browse the repository at this point in the history
  • Loading branch information
yvt committed May 25, 2020
1 parent 62902bf commit f6d4868
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 6 deletions.
16 changes: 14 additions & 2 deletions tcw3/meta/views/entry.tcwdl
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ pub comp crate::ui::views::Entry {
/// When you assign to this property, if the new value is different from the
/// current one, it resets various internal states such as an undo history.
/// Otherwise, it does nothing.
prop text: String { pub set; pub get clone; } = ?;
prop text: String { pub set; pub get clone; pub watch event(changed); } = ?;

/// Raised after the text content is modified.
///
/// The event may be raised spuriously, i.e., even when the text content
/// is not actually modified.
pub event changed(wm: pal::Wm);
}

#[prototype_only]
Expand All @@ -37,5 +43,11 @@ pub comp crate::ui::views::EntryCore {
/// When you assign to this property, if the new value is different from the
/// current one, it resets various internal states such as an undo history.
/// Otherwise, it does nothing.
prop text: String { pub set; pub get clone; } = ?;
prop text: String { pub set; pub get clone; pub watch event(changed); } = ?;

/// Raised after the text content is modified.
///
/// The event may be raised spuriously, i.e., even when the text content
/// is not actually modified.
pub event changed(wm: pal::Wm);
}
75 changes: 73 additions & 2 deletions tcw3/src/ui/views/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ use momo::momo;
use rc_borrow::RcBorrow;
use std::{
cell::{Cell, RefCell, RefMut},
fmt,
ops::Range,
rc::Rc,
};
use subscriber_list::SubscriberList;
use unicount::{str_ceil, str_floor, str_prev};

use crate::{
Expand All @@ -25,7 +27,7 @@ use crate::{
},
uicore::{
actions, ActionId, ActionStatus, CursorShape, HView, HViewRef, HWndRef, MouseDragListener,
SizeTraits, UpdateCtx, ViewFlags, ViewListener, WeakHView,
SizeTraits, Sub, UpdateCtx, ViewFlags, ViewListener, WeakHView, WmExt,
},
};

Expand Down Expand Up @@ -98,6 +100,13 @@ impl Entry {
pub fn set_text(&self, value: impl Into<String>) {
self.core.set_text(value)
}

/// Add a function called after the text content is modified.
///
/// See [`EntryCore::subscribe_changed`].
pub fn subscribe_changed(&self, cb: Box<dyn Fn(pal::Wm)>) -> Sub {
self.core.subscribe_changed(cb)
}
}

impl Widget for Entry {
Expand All @@ -123,14 +132,32 @@ pub struct EntryCore {
inner: Rc<Inner>,
}

#[derive(Debug)]
struct Inner {
wm: pal::Wm,
view: WeakHView,
state: RefCell<State>,
style_elem: theming::Elem,
style_sel_elem: theming::Elem,
tictx_event_mask: Cell<pal::TextInputCtxEventFlags>,

/// The list of subscribers of the `change` event.
change_handlers: RefCell<SubscriberList<Box<dyn Fn(pal::Wm)>>>,
/// `true` means the calls to `change_handlers` are pended.
pending_change_handler: Cell<bool>,
}

impl fmt::Debug for Inner {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Inner")
.field("wm", &self.wm)
.field("view", &self.view)
.field("state", &self.state)
.field("style_elem", &self.style_elem)
.field("style_sel_elem", &self.style_sel_elem)
.field("tictx_event_mask", &self.tictx_event_mask)
.field("pending_change_handler", &self.pending_change_handler)
.finish()
}
}

#[derive(Debug)]
Expand Down Expand Up @@ -204,6 +231,8 @@ impl EntryCore {
style_elem,
style_sel_elem,
tictx_event_mask: Cell::new(pal::TextInputCtxEventFlags::empty()),
change_handlers: RefCell::new(SubscriberList::new()),
pending_change_handler: Cell::new(false),
}),
};

Expand Down Expand Up @@ -281,6 +310,18 @@ impl EntryCore {
},
);
}

/// Add a function called when the text content is modified.
///
/// The function may be called spuriously, i.e., even when the text content
/// is not actually modified.
///
/// The function is called via `Wm::invoke`, thus allowed to modify
/// view hierarchy and view attributes. However, it's not allowed to call
/// `subscribe_changed` when one of the handlers is being called.
pub fn subscribe_changed(&self, cb: Box<dyn Fn(pal::Wm)>) -> Sub {
self.inner.change_handlers.borrow_mut().insert(cb).untype()
}
}

impl State {
Expand Down Expand Up @@ -1234,6 +1275,10 @@ impl Drop for Edit<'_> {

if let Some(history_tx) = self.history_tx.take() {
history_tx.finish(&mut state.history, &state.text);

// `text` might have changed, so raise `changed`
// (False positives are positive because of `set_composition_range`)
pend_raise_change(self.inner);
}

if self
Expand Down Expand Up @@ -1482,6 +1527,11 @@ fn update_state(
state.caret_blink = true;
state.reset_timer(hview, inner, None);

// Raise `changed`
if flags.contains(UpdateStateFlags::ANY) {
pend_raise_change(inner);
}

// Invalidate the remembered caret position
state.caret = None;

Expand All @@ -1502,6 +1552,27 @@ fn update_state(
}
}

/// Pend calls to the `change` event handlers.
fn pend_raise_change(inner: RcBorrow<'_, Inner>) {
if inner.pending_change_handler.get() {
return;
}

// FIXME: Remove this extra `upgrade`-ing
let inner_weak = Rc::downgrade(&RcBorrow::upgrade(inner));

inner.wm.invoke_on_update(move |wm| {
if let Some(inner) = inner_weak.upgrade() {
inner.pending_change_handler.set(false);

let handlers = inner.change_handlers.borrow();
for handler in handlers.iter() {
handler(wm);
}
}
});
}

struct EntryCoreDragListener {
view: HView,
inner: Rc<Inner>,
Expand Down
22 changes: 20 additions & 2 deletions tcw3/src/ui/views/entry/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ use crate::{
uicore::{HView, HWnd, SizeTraits, ViewFlags},
};
use cggeom::prelude::*;
use enclose::enc;
use log::info;
use std::{cell::RefCell, rc::Rc};
use try_match::try_match;

use super::Entry;
Expand Down Expand Up @@ -78,14 +80,15 @@ struct TestWithOneEntry {
wm: pal::Wm,
hwnd: HWnd,
pal_hwnd: pal::HWnd,
entry: Entry,
entry: Rc<Entry>,
changed_events: Rc<RefCell<Vec<String>>>,
}
fn init_test_with_one_entry(twm: &dyn TestingWm) -> TestWithOneEntry {
let wm = twm.wm();

let style_manager = Manager::global(wm);

let entry = Entry::new(wm, style_manager);
let entry = Rc::new(Entry::new(wm, style_manager));

let wnd = HWnd::new(wm);
wnd.content_view().set_layout(TableLayout::stack_vert(vec![
Expand All @@ -106,11 +109,21 @@ fn init_test_with_one_entry(twm: &dyn TestingWm) -> TestWithOneEntry {
twm.set_wnd_focused(&pal_hwnd, true);
twm.step_unsend();

// Register a `changed` event handler
let changed_events = Rc::new(RefCell::new(Vec::new()));
let entry_weak = Rc::downgrade(&entry);
entry.subscribe_changed(Box::new(enc!((changed_events) move |_| {
if let Some(entry) = entry_weak.upgrade() {
changed_events.borrow_mut().push(entry.text());
}
})));

TestWithOneEntry {
wm,
hwnd: wnd,
pal_hwnd,
entry,
changed_events,
}
}

Expand All @@ -121,6 +134,7 @@ fn set_text(twm: &dyn TestingWm) {
entry,
hwnd: _hwnd,
pal_hwnd,
changed_events,
..
} = init_test_with_one_entry(twm);

Expand All @@ -137,6 +151,7 @@ fn set_text(twm: &dyn TestingWm) {

assert!(entry.core().inner.state.borrow().history.can_undo());
assert_eq!(entry.text(), "hello");
assert_eq!(changed_events.borrow()[..], ["hello"][..]);

// Assign a new text
entry.set_text("world");
Expand All @@ -147,4 +162,7 @@ fn set_text(twm: &dyn TestingWm) {

// .. and that history should be forgotten
assert!(!entry.core().inner.state.borrow().history.can_undo());

// .. and a `changed` event should be generated
assert_eq!(changed_events.borrow()[..], ["hello", "world"][..]);
}

0 comments on commit f6d4868

Please sign in to comment.