Trait druid::LensExt

source ·
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 Lenses

Provided Methods§

source

fn get(&self, data: &A) -> Bwhere B: Clone,

Copy the targeted value out of data

source

fn put(&self, data: &mut A, value: B)where B: Sized,

Set the targeted value in data to value

source

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?
examples/sub_window.rs (line 346)
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)
}
source

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?
examples/switches.rs (line 58)
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
Hide additional examples
examples/styled_text.rs (line 135)
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()
}
examples/list.rs (lines 118-125)
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()
}
source

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);
source

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))
}
source

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);
source

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]);
source

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);

Implementors§

source§

impl<A: ?Sized, B: ?Sized, T: Lens<A, B>> LensExt<A, B> for T