pub trait LensExt<A: ?Sized, B: ?Sized>: Lens<A, B> {
// Provided methods
fn get(&self, data: &A) -> B
where B: Clone { ... }
fn put(&self, data: &mut A, value: B)
where B: Sized { ... }
fn then<Other, C>(self, other: Other) -> Then<Self, Other, B>
where Other: Lens<B, C> + Sized,
C: ?Sized,
Self: Sized { ... }
fn map<Get, Put, C>(self, get: Get, put: Put) -> Then<Self, Map<Get, Put>, B>
where Get: Fn(&B) -> C,
Put: Fn(&mut B, C),
Self: Sized { ... }
fn deref(self) -> Then<Self, Deref, B>
where B: Deref + DerefMut,
Self: Sized { ... }
fn as_ref<T: ?Sized>(self) -> Then<Self, Ref, B>
where B: AsRef<T> + AsMut<T>,
Self: Sized { ... }
fn index<I>(self, index: I) -> Then<Self, Index<I>, B>
where I: Clone,
B: Index<I> + IndexMut<I>,
Self: Sized { ... }
fn in_arc(self) -> InArc<Self>
where A: Clone,
B: Data,
Self: Sized { ... }
fn not(self) -> Then<Self, Not, B>
where Self: Sized,
B: Sized + Into<bool> + Copy,
bool: Into<B> { ... }
}
Expand description
Helpers for manipulating Lens
es
Provided Methods§
sourcefn then<Other, C>(self, other: Other) -> Then<Self, Other, B>where
Other: Lens<B, C> + Sized,
C: ?Sized,
Self: Sized,
fn then<Other, C>(self, other: Other) -> Then<Self, Other, B>where Other: Lens<B, C> + Sized, C: ?Sized, Self: Sized,
Compose a Lens<A, B>
with a Lens<B, C>
to produce a Lens<A, C>
struct Foo { x: (u32, bool) }
let lens = lens!(Foo, x).then(lens!((u32, bool), 1));
assert_eq!(lens.get(&Foo { x: (0, true) }), true);
Examples found in repository?
332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
fn build_root_widget() -> impl Widget<HelloState> {
let label = EnvScope::new(
|env, _t| env.set(theme::TEXT_COLOR, env.get(theme::PRIMARY_LIGHT)),
ControllerHost::new(
Label::new(|data: &HelloState, _env: &Env| {
format!("Hello {}! {} ", data.name, data.sub.my_stuff)
}),
TooltipController::new("Tips! Are good"),
),
);
// a textbox that modifies `name`.
let textbox = TextBox::new()
.with_placeholder("Who are we greeting?")
.fix_width(TEXT_BOX_WIDTH)
.lens(HelloState::sub.then(SubState::my_stuff));
let button = Button::new("Make sub window")
.on_click(|ctx, data: &mut SubState, env| {
let tb = TextBox::new().lens(SubState::my_stuff);
let drag_thing = Label::new("Drag me").controller(DragWindowController::new());
let col = Flex::column().with_child(drag_thing).with_child(tb);
ctx.new_sub_window(
WindowConfig::default()
.show_titlebar(false)
.window_size(Size::new(100., 100.))
.set_level(WindowLevel::AppWindow),
col,
data.clone(),
env.clone(),
);
})
.center()
.lens(HelloState::sub);
let check_box =
ControllerHost::new(Checkbox::new("Closeable?"), CancelClose).lens(HelloState::closeable);
// arrange the two widgets vertically, with some padding
let layout = Flex::column()
.with_child(label)
.with_flex_child(ScreenThing.lens(Unit::default()).padding(5.), 1.)
.with_spacer(VERTICAL_WIDGET_SPACING)
.with_child(textbox)
.with_child(button)
.with_child(check_box);
// center the two widgets in the available space
Align::centered(layout)
}
sourcefn map<Get, Put, C>(self, get: Get, put: Put) -> Then<Self, Map<Get, Put>, B>where
Get: Fn(&B) -> C,
Put: Fn(&mut B, C),
Self: Sized,
fn map<Get, Put, C>(self, get: Get, put: Put) -> Then<Self, Map<Get, Put>, B>where Get: Fn(&B) -> C, Put: Fn(&mut B, C), Self: Sized,
Combine a Lens<A, B>
with a function that can transform a B
and its inverse.
Useful for cases where the desired value doesn’t physically exist in A
, but can be
computed. For example, a lens like the following might be used to adapt a value with the
range 0-2 for use with a Widget<f64>
like Slider
that has a range of 0-1:
let lens = lens!((bool, f64), 1);
assert_eq!(lens.map(|x| x / 2.0, |x, y| *x = y * 2.0).get(&(true, 2.0)), 1.0);
The computed C
may represent a whole or only part of the original B
.
Examples found in repository?
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
fn build_widget() -> impl Widget<DemoState> {
let mut col = Flex::column();
let mut row = Flex::row();
let switch = LensWrap::new(Switch::new(), DemoState::value);
let check_box = LensWrap::new(Checkbox::new(""), DemoState::value);
let switch_label = Label::new("Setting label");
row.add_child(Padding::new(5.0, switch_label));
row.add_child(Padding::new(5.0, switch));
row.add_child(Padding::new(5.0, check_box));
let stepper = LensWrap::new(
Stepper::new()
.with_range(0.0, 10.0)
.with_step(0.5)
.with_wraparound(false),
DemoState::stepper_value,
);
let mut textbox_row = Flex::row();
// TODO: Replace Parse usage with TextBox::with_formatter
#[allow(deprecated)]
let textbox = LensWrap::new(
Parse::new(TextBox::new()),
DemoState::stepper_value.map(|x| Some(*x), |x, y| *x = y.unwrap_or(0.0)),
);
textbox_row.add_child(Padding::new(5.0, textbox));
textbox_row.add_child(Padding::new(5.0, stepper.center()));
let mut label_row = Flex::row();
let label = Label::new(|data: &DemoState, _env: &_| {
format!("Stepper value: {0:.2}", data.stepper_value)
});
label_row.add_child(Padding::new(5.0, label));
col.set_main_axis_alignment(MainAxisAlignment::Center);
col.add_child(Padding::new(5.0, row));
col.add_child(Padding::new(5.0, textbox_row));
col.add_child(Padding::new(5.0, label_row));
col.center()
}
More examples
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
fn ui_builder() -> impl Widget<AppData> {
let my_painter = Painter::new(|ctx, _, _| {
let bounds = ctx.size().to_rect();
if ctx.is_hot() {
ctx.fill(bounds, &Color::rgba8(0, 0, 0, 128));
}
if ctx.is_active() {
ctx.stroke(bounds, &Color::WHITE, 2.0);
}
});
// This is Druid's default text style.
// It's set by theme::LABEL_COLOR and theme::UI_FONT
let label =
Label::new(|data: &String, _env: &_| format!("Default: {data}")).lens(AppData::text);
// The text_color, text_size, and font builder methods can override the
// defaults provided by the theme by passing in a Key or a concrete value.
//
// In this example, text_color receives a Key from the theme, text_size
// gets a custom key which we set with the env_scope wrapper, and the
// default font key (theme::FONT_NAME) is overridden in the env_scope
// wrapper. (Like text_color and text_size, the font can be set using the
// with_font builder method, but overriding here makes it easy to fall back
// to the default font)
let styled_label = Label::new(|data: &AppData, _env: &_| format!("{data}"))
.with_text_color(theme::PRIMARY_LIGHT)
.with_font(MY_CUSTOM_FONT)
.background(my_painter)
.on_click(|_, data, _| {
data.size *= 1.1;
})
.env_scope(|env: &mut druid::Env, data: &AppData| {
let new_font = if data.mono {
FontDescriptor::new(FontFamily::MONOSPACE)
} else {
FontDescriptor::new(FontFamily::SYSTEM_UI)
}
.with_size(data.size);
env.set(MY_CUSTOM_FONT, new_font);
});
let labels = Scroll::new(
Flex::column()
.cross_axis_alignment(CrossAxisAlignment::Start)
.with_child(label)
.with_default_spacer()
.with_child(styled_label),
)
.expand_height()
.fix_width(COLUMN_WIDTH);
let stepper = Stepper::new()
.with_range(0.0, 100.0)
.with_step(1.0)
.with_wraparound(false)
.lens(AppData::size);
// TODO: Replace Parse usage with TextBox::with_formatter
#[allow(deprecated)]
let stepper_textbox = LensWrap::new(
Parse::new(TextBox::new()),
AppData::size.map(|x| Some(*x), |x, y| *x = y.unwrap_or(24.0)),
);
let mono_checkbox = Checkbox::new("Monospace").lens(AppData::mono);
let stepper_row = Flex::row()
.with_child(stepper_textbox)
.with_child(stepper)
.with_default_spacer()
.with_child(mono_checkbox);
let input = TextBox::multiline()
.with_placeholder("Your sample text here :)")
.fix_width(COLUMN_WIDTH)
.fix_height(140.0)
.lens(AppData::text);
Flex::column()
.main_axis_alignment(MainAxisAlignment::Center)
.with_default_spacer()
.with_flex_child(labels, 1.0)
.with_default_spacer()
.with_child(input)
.with_default_spacer()
.with_child(stepper_row)
.with_default_spacer()
}
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
fn ui_builder() -> impl Widget<AppData> {
let mut root = Flex::column();
// Build a button to add children to both lists
root.add_child(
Button::new("Add")
.on_click(|_, data: &mut AppData, _| {
// Add child to left list
data.l_index += 1;
data.left.push_back(data.l_index as u32);
// Add child to right list
data.r_index += 1;
data.right.push_back(data.r_index as u32);
})
.fix_height(30.0)
.expand_width(),
);
let mut lists = Flex::row().cross_axis_alignment(CrossAxisAlignment::Start);
// Build a simple list
lists.add_flex_child(
Scroll::new(List::new(|| {
Label::new(|item: &u32, _env: &_| format!("List item #{item}"))
.align_vertical(UnitPoint::LEFT)
.padding(10.0)
.expand()
.height(50.0)
.background(Color::rgb(0.5, 0.5, 0.5))
}))
.vertical()
.lens(AppData::left),
1.0,
);
// Build a list with shared data
lists.add_flex_child(
Scroll::new(
List::new(|| {
Flex::row()
.with_child(
Label::new(|(_, item): &(Vector<u32>, u32), _env: &_| {
format!("List item #{item}")
})
.align_vertical(UnitPoint::LEFT),
)
.with_flex_spacer(1.0)
.with_child(
Button::new("Delete")
.on_click(|_ctx, (shared, item): &mut (Vector<u32>, u32), _env| {
// We have access to both child's data and shared data.
// Remove element from right list.
shared.retain(|v| v != item);
})
.fix_size(80.0, 20.0)
.align_vertical(UnitPoint::CENTER),
)
.padding(10.0)
.background(Color::rgb(0.5, 0.0, 0.5))
.fix_height(50.0)
})
.with_spacing(10.),
)
.vertical()
.lens(lens::Identity.map(
// Expose shared data with children data
|d: &AppData| (d.right.clone(), d.right.clone()),
|d: &mut AppData, x: (Vector<u32>, Vector<u32>)| {
// If shared data was changed reflect the changes in our AppData
d.right = x.0
},
)),
1.0,
);
root.add_flex_child(lists, 1.0);
root.with_child(Label::new("horizontal list"))
.with_child(
Scroll::new(
List::new(|| {
Label::new(|item: &u32, _env: &_| format!("List item #{item}"))
.padding(10.0)
.background(Color::rgb(0.5, 0.5, 0.0))
.fix_height(50.0)
})
.horizontal()
.with_spacing(10.)
.lens(AppData::left),
)
.horizontal(),
)
.debug_paint_layout()
}
sourcefn deref(self) -> Then<Self, Deref, B>where
B: Deref + DerefMut,
Self: Sized,
fn deref(self) -> Then<Self, Deref, B>where B: Deref + DerefMut, Self: Sized,
Invoke a type’s Deref
impl
assert_eq!(lens::Identity.deref().get(&Box::new(42)), 42);
sourcefn as_ref<T: ?Sized>(self) -> Then<Self, Ref, B>where
B: AsRef<T> + AsMut<T>,
Self: Sized,
fn as_ref<T: ?Sized>(self) -> Then<Self, Ref, B>where B: AsRef<T> + AsMut<T>, Self: Sized,
Invoke a type’s AsRef
and AsMut
impl.
It also allows indexing arrays with the index
lens as shown in the example.
This is necessary, because the Index
trait in Rust is only implemented
for slices ([T]
), but not for arrays ([T; N]
).
Examples
Using ref
this works:
use druid::{widget::TextBox, Data, Lens, LensExt, Widget, WidgetExt};
#[derive(Clone, Default, Data, Lens)]
struct State {
data: [String; 2],
}
fn with_ref() -> impl Widget<State> {
TextBox::new().lens(State::data.as_ref().index(1))
}
While this fails:
fn without_ref() -> impl Widget<State> {
// results in: `[std::string::String; 2]` cannot be mutably indexed by `usize`
TextBox::new().lens(State::data.index(1))
}
sourcefn index<I>(self, index: I) -> Then<Self, Index<I>, B>where
I: Clone,
B: Index<I> + IndexMut<I>,
Self: Sized,
fn index<I>(self, index: I) -> Then<Self, Index<I>, B>where I: Clone, B: Index<I> + IndexMut<I>, Self: Sized,
Access an index in a container
assert_eq!(lens::Identity.index(2).get(&vec![0u32, 1, 2, 3]), 2);
sourcefn in_arc(self) -> InArc<Self>where
A: Clone,
B: Data,
Self: Sized,
fn in_arc(self) -> InArc<Self>where A: Clone, B: Data, Self: Sized,
Adapt to operate on the contents of an Arc
with efficient copy-on-write semantics
let lens = lens::Identity.index(2).in_arc();
let mut x = Arc::new(vec![0, 1, 2, 3]);
let original = x.clone();
assert_eq!(lens.get(&x), 2);
lens.put(&mut x, 2);
assert!(Arc::ptr_eq(&original, &x), "no-op writes don't cause a deep copy");
lens.put(&mut x, 42);
assert_eq!(&*x, &[0, 1, 42, 3]);
sourcefn not(self) -> Then<Self, Not, B>where
Self: Sized,
B: Sized + Into<bool> + Copy,
bool: Into<B>,
fn not(self) -> Then<Self, Not, B>where Self: Sized, B: Sized + Into<bool> + Copy, bool: Into<B>,
A lens that reverses a boolean value
Examples
use druid::LensExt;
#[derive(Lens)]
struct MyThing {
first: bool
}
let lens = MyThing::first.not();
let mut val = MyThing { first: false };
assert_eq!(lens.with(&val, |v| *v), true);
lens.with_mut(&mut val, |v| *v = false);
assert_eq!(val.first, true);