Skip to content

Commit 4b4dc7c

Browse files
committed
Scope cherry-picked from binding-scroll branch.
1 parent 61c9882 commit 4b4dc7c

File tree

6 files changed

+214
-1
lines changed

6 files changed

+214
-1
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ Kaiyin Zhong
1111
Kaur Kuut
1212
Leopold Luley
1313
Andrey Kabylin
14+
Robert Wittams

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ You can find its changes [documented below](#060---2020-06-01).
1717
- Close requests from the shell can now be intercepted ([#1118] by [@jneem])
1818
- The Lens derive now supports an `ignore` attribute. ([#1133] by [@jneem])
1919
- `request_update` in `EventCtx`. ([#1128] by [@raphlinus])
20+
- 'Scope' widget to allow encapsulation of reactive state. ([#1151] by [@rjwittams])
2021

2122
### Changed
2223

@@ -397,6 +398,7 @@ Last release without a changelog :(
397398
[#1133]: https://github.com/linebender/druid/pull/1133
398399
[#1143]: https://github.com/linebender/druid/pull/1143
399400
[#1145]: https://github.com/linebender/druid/pull/1145
401+
[#1151]: https://github.com/linebender/druid/pull/1151
400402

401403
[Unreleased]: https://github.com/linebender/druid/compare/v0.6.0...master
402404
[0.6.0]: https://github.com/linebender/druid/compare/v0.5.0...v0.6.0

druid/src/contexts.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,12 @@ impl EventCtx<'_, '_> {
501501
}
502502
}
503503

504+
impl UpdateCtx<'_, '_> {
505+
pub fn has_requested_update(&mut self) -> bool {
506+
self.widget_state.request_update
507+
}
508+
}
509+
504510
impl LifeCycleCtx<'_, '_> {
505511
/// Registers a child widget.
506512
///

druid/src/lens/lens.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ where
232232
let lens = &self.lens;
233233
lens.with(old_data, |old_data| {
234234
lens.with(data, |data| {
235-
if !old_data.same(data) {
235+
if ctx.has_requested_update() || !old_data.same(data) {
236236
inner.update(ctx, old_data, data, env);
237237
}
238238
})

druid/src/widget/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ mod painter;
3434
mod parse;
3535
mod progress_bar;
3636
mod radio;
37+
mod scope;
3738
mod scroll;
3839
mod sized_box;
3940
mod slider;
@@ -69,6 +70,7 @@ pub use painter::{BackgroundBrush, Painter};
6970
pub use parse::Parse;
7071
pub use progress_bar::ProgressBar;
7172
pub use radio::{Radio, RadioGroup};
73+
pub use scope::{DefaultScopePolicy, LensScopeTransfer, Scope, ScopePolicy, ScopeTransfer};
7274
pub use scroll::Scroll;
7375
pub use sized_box::SizedBox;
7476
pub use slider::Slider;

druid/src/widget/scope.rs

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
use crate::kurbo::{Point, Rect};
2+
use crate::{
3+
BoxConstraints, Data, Env, Event, EventCtx, LayoutCtx, Lens, LifeCycle, LifeCycleCtx, PaintCtx,
4+
Size, UpdateCtx, Widget, WidgetPod,
5+
};
6+
use std::marker::PhantomData;
7+
8+
pub trait ScopePolicy {
9+
type In: Data;
10+
type State: Data;
11+
type Transfer: ScopeTransfer<In = Self::In, State = Self::State>;
12+
// Make a new state and transfer from the input.
13+
// This consumes the policy, so non cloneable items can make their way into the state this way.
14+
fn create(self, inner: &Self::In) -> (Self::State, Self::Transfer);
15+
}
16+
17+
pub trait ScopeTransfer {
18+
type In: Data;
19+
type State: Data;
20+
21+
// Replace the input we have with a new one from outside
22+
fn read_input(&self, state: &mut Self::State, inner: &Self::In);
23+
// Take the modifications we have made and write them back
24+
// to our input.
25+
fn write_back_input(&self, state: &Self::State, inner: &mut Self::In);
26+
}
27+
28+
pub struct DefaultScopePolicy<F: Fn(Transfer::In) -> Transfer::State, Transfer: ScopeTransfer> {
29+
make_state: F,
30+
transfer: Transfer,
31+
}
32+
33+
impl<F: Fn(Transfer::In) -> Transfer::State, Transfer: ScopeTransfer>
34+
DefaultScopePolicy<F, Transfer>
35+
{
36+
pub fn new(make_state: F, transfer: Transfer) -> Self {
37+
DefaultScopePolicy {
38+
make_state,
39+
transfer,
40+
}
41+
}
42+
}
43+
44+
impl<F: Fn(In) -> State, L: Lens<State, In>, In: Data, State: Data>
45+
DefaultScopePolicy<F, LensScopeTransfer<L, In, State>>
46+
{
47+
pub fn for_lens(make_state: F, lens: L) -> Self {
48+
Self::new(make_state, LensScopeTransfer::new(lens))
49+
}
50+
}
51+
52+
impl<F: Fn(Transfer::In) -> Transfer::State, Transfer: ScopeTransfer> ScopePolicy
53+
for DefaultScopePolicy<F, Transfer>
54+
{
55+
type In = Transfer::In;
56+
type State = Transfer::State;
57+
type Transfer = Transfer;
58+
59+
fn create(self, inner: &Self::In) -> (Self::State, Self::Transfer) {
60+
let state = (self.make_state)(inner.clone());
61+
(state, self.transfer)
62+
}
63+
}
64+
65+
pub struct LensScopeTransfer<L: Lens<State, In>, In, State> {
66+
lens: L,
67+
phantom_in: PhantomData<In>,
68+
phantom_state: PhantomData<State>,
69+
}
70+
71+
impl<L: Lens<State, In>, In, State> LensScopeTransfer<L, In, State> {
72+
pub fn new(lens: L) -> Self {
73+
LensScopeTransfer {
74+
lens,
75+
phantom_in: PhantomData::default(),
76+
phantom_state: PhantomData::default(),
77+
}
78+
}
79+
}
80+
81+
impl<L: Lens<State, In>, In: Data, State: Data> ScopeTransfer for LensScopeTransfer<L, In, State> {
82+
type In = In;
83+
type State = State;
84+
85+
fn read_input(&self, state: &mut State, data: &In) {
86+
self.lens.with_mut(state, |inner| {
87+
if !inner.same(&data) {
88+
*inner = data.clone()
89+
}
90+
});
91+
}
92+
93+
fn write_back_input(&self, state: &State, data: &mut In) {
94+
self.lens.with(state, |inner| {
95+
if !inner.same(&data) {
96+
*data = inner.clone();
97+
}
98+
});
99+
}
100+
}
101+
102+
enum ScopeContent<SP: ScopePolicy> {
103+
Policy {
104+
policy: Option<SP>,
105+
},
106+
Transfer {
107+
state: SP::State,
108+
transfer: SP::Transfer,
109+
},
110+
}
111+
112+
pub struct Scope<SP: ScopePolicy, W: Widget<SP::State>> {
113+
content: ScopeContent<SP>,
114+
inner: WidgetPod<SP::State, W>,
115+
widget_added: bool,
116+
}
117+
118+
impl<SP: ScopePolicy, W: Widget<SP::State>> Scope<SP, W> {
119+
pub fn new(policy: SP, inner: W) -> Self {
120+
Scope {
121+
content: ScopeContent::Policy {
122+
policy: Some(policy),
123+
},
124+
inner: WidgetPod::new(inner),
125+
widget_added: false,
126+
}
127+
}
128+
129+
fn with_state<V>(
130+
&mut self,
131+
data: &SP::In,
132+
mut f: impl FnMut(&mut SP::State, &mut WidgetPod<SP::State, W>) -> V,
133+
) -> V {
134+
match &mut self.content {
135+
ScopeContent::Policy { policy } => {
136+
// We know that the policy is a Some - it is an option to allow
137+
// us to take ownership before replacing the content.
138+
let (mut state, policy) = policy.take().unwrap().create(data);
139+
let v = f(&mut state, &mut self.inner);
140+
self.content = ScopeContent::Transfer {
141+
state,
142+
transfer: policy,
143+
};
144+
v
145+
}
146+
ScopeContent::Transfer {
147+
ref mut state,
148+
transfer: policy,
149+
} => {
150+
policy.read_input(state, data);
151+
f(state, &mut self.inner)
152+
}
153+
}
154+
}
155+
156+
fn write_back_input(&mut self, data: &mut SP::In) {
157+
if let ScopeContent::Transfer { state, transfer } = &mut self.content {
158+
transfer.write_back_input(state, data)
159+
}
160+
}
161+
}
162+
163+
impl<SP: ScopePolicy, W: Widget<SP::State>> Widget<SP::In> for Scope<SP, W> {
164+
fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut SP::In, env: &Env) {
165+
if self.widget_added {
166+
self.with_state(data, |state, inner| inner.event(ctx, event, state, env));
167+
self.write_back_input(data);
168+
ctx.request_update()
169+
} else {
170+
log::warn!("Scope dropping event (widget not added) {:?}", event);
171+
}
172+
}
173+
174+
fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &SP::In, env: &Env) {
175+
self.with_state(data, |state, inner| inner.lifecycle(ctx, event, state, env));
176+
if let LifeCycle::WidgetAdded = event {
177+
self.widget_added = true;
178+
}
179+
}
180+
181+
fn update(&mut self, ctx: &mut UpdateCtx, _old_data: &SP::In, data: &SP::In, env: &Env) {
182+
self.with_state(data, |state, inner| inner.update(ctx, state, env));
183+
}
184+
185+
fn layout(
186+
&mut self,
187+
ctx: &mut LayoutCtx,
188+
bc: &BoxConstraints,
189+
data: &SP::In,
190+
env: &Env,
191+
) -> Size {
192+
self.with_state(data, |state, inner| {
193+
let size = inner.layout(ctx, bc, state, env);
194+
inner.set_layout_rect(ctx, state, env, Rect::from_origin_size(Point::ORIGIN, size));
195+
size
196+
})
197+
}
198+
199+
fn paint(&mut self, ctx: &mut PaintCtx, data: &SP::In, env: &Env) {
200+
self.with_state(data, |state, inner| inner.paint_raw(ctx, state, env));
201+
}
202+
}

0 commit comments

Comments
 (0)