Skip to content

Commit 7cab11a

Browse files
committed
Expose derived lenses as associated constants
1 parent 8dd855a commit 7cab11a

File tree

6 files changed

+28
-89
lines changed

6 files changed

+28
-89
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,8 @@ struct AppState {
188188
### lens
189189

190190
The [Lens datatype] gives access to a part of a larger data structure. Like
191-
`Data`, This can be derived.
191+
`Data`, this can be derived. Derived lenses are accessed as associated constants
192+
with the same name as the field.
192193

193194
```rust
194195
#[derive(Clone, Data, Lens)]
@@ -202,7 +203,7 @@ To use the lens, wrap your widget with `LensWrap` (note the conversion of
202203
CamelCase to snake_case):
203204

204205
```rust
205-
LensWrap::new(WidgetThatExpectsf64::new(), lenses::app_state::value);
206+
LensWrap::new(WidgetThatExpectsf64::new(), AppState::value);
206207
```
207208

208209
## Using druid

druid-derive/src/lens.rs

Lines changed: 18 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -47,32 +47,16 @@ fn derive_struct(input: &syn::DeriveInput) -> Result<proc_macro2::TokenStream, s
4747
));
4848
};
4949

50-
let twizzled_name = if is_camel_case(&ty.to_string()) {
51-
let temp_name = to_snake_case(&ty.to_string());
52-
proc_macro2::Ident::new(&temp_name, proc_macro2::Span::call_site())
53-
} else {
54-
return Err(syn::Error::new(
55-
ty.span(),
56-
"Lens implementations can only be derived from CamelCase types",
57-
));
58-
};
59-
60-
// Declare a struct for each field
61-
let structs = fields.iter().map(|f| {
62-
let field_name = &f.ident;
63-
64-
quote! {
65-
#[allow(non_camel_case_types)]
66-
pub struct #field_name;
67-
}
68-
});
50+
let mod_name = proc_macro2::Ident::new(&format!("__{}_druid_lenses", &ty), proc_macro2::Span::call_site());
6951

7052
// Impl Lens for each field
7153
let impls = fields.iter().map(|f| {
7254
let field_name = &f.ident;
7355
let field_ty = &f.ty;
7456

7557
quote! {
58+
#[allow(non_camel_case_types)]
59+
pub struct #field_name;
7660

7761
impl Lens<#ty, #field_ty> for #field_name {
7862
fn with<V, F: FnOnce(&#field_ty) -> V>(&self, data: &#ty, f: F) -> V {
@@ -86,69 +70,26 @@ fn derive_struct(input: &syn::DeriveInput) -> Result<proc_macro2::TokenStream, s
8670
}
8771
});
8872

73+
let associated_items = fields.iter().map(|f| {
74+
let field_name = &f.ident;
75+
quote! {
76+
pub const #field_name: #field_name = #field_name;
77+
}
78+
});
79+
8980
let expanded = quote! {
90-
pub mod lenses {
91-
pub mod #twizzled_name {
92-
use super::super::*;
81+
mod #mod_name {
82+
use super::*;
83+
use druid::Lens;
9384

94-
use druid::Lens;
95-
#(#structs)*
96-
#(#impls)*
97-
}
85+
#(#impls)*
9886

87+
#[allow(non_upper_case_globals)]
88+
impl #ty {
89+
#(#associated_items)*
90+
}
9991
}
10092
};
10193

10294
Ok(expanded)
10395
}
104-
105-
//I stole these from rustc!
106-
fn char_has_case(c: char) -> bool {
107-
c.is_lowercase() || c.is_uppercase()
108-
}
109-
110-
fn is_camel_case(name: &str) -> bool {
111-
let name = name.trim_matches('_');
112-
if name.is_empty() {
113-
return true;
114-
}
115-
116-
// start with a non-lowercase letter rather than non-uppercase
117-
// ones (some scripts don't have a concept of upper/lowercase)
118-
!name.chars().next().unwrap().is_lowercase()
119-
&& !name.contains("__")
120-
&& !name.chars().collect::<Vec<_>>().windows(2).any(|pair| {
121-
// contains a capitalisable character followed by, or preceded by, an underscore
122-
char_has_case(pair[0]) && pair[1] == '_' || char_has_case(pair[1]) && pair[0] == '_'
123-
})
124-
}
125-
126-
fn to_snake_case(mut str: &str) -> String {
127-
let mut words = vec![];
128-
// Preserve leading underscores
129-
str = str.trim_start_matches(|c: char| {
130-
if c == '_' {
131-
words.push(String::new());
132-
true
133-
} else {
134-
false
135-
}
136-
});
137-
for s in str.split('_') {
138-
let mut last_upper = false;
139-
let mut buf = String::new();
140-
if s.is_empty() {
141-
continue;
142-
}
143-
for ch in s.chars() {
144-
if !buf.is_empty() && buf != "'" && ch.is_uppercase() && !last_upper {
145-
words.push(buf);
146-
buf = String::new();
147-
}
148-
last_upper = ch.is_uppercase();
149-
buf.extend(ch.to_lowercase());
150-
}
151-
words.push(buf);
152-
}
153-
words.join("_")
154-
}

druid/examples/calc.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ fn build_calc() -> impl Widget<CalcState> {
152152
let mut column = Flex::column();
153153
let display = LensWrap::new(
154154
DynLabel::new(|data: &String, _env| data.clone()),
155-
lenses::calc_state::value,
155+
CalcState::value,
156156
);
157157
column.add_child(pad(display), 0.0);
158158
column.add_child(

druid/examples/either.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,12 @@ fn ui_builder() -> impl Widget<AppState> {
3535

3636
let mut col = Flex::column();
3737
col.add_child(
38-
Padding::new(
39-
5.0,
40-
LensWrap::new(Checkbox::new(), lenses::app_state::which),
41-
),
38+
Padding::new(5.0, LensWrap::new(Checkbox::new(), AppState::which)),
4239
0.0,
4340
);
4441
let either = Either::new(
4542
|data, _env| data.which,
46-
Padding::new(5.0, LensWrap::new(Slider::new(), lenses::app_state::value)),
43+
Padding::new(5.0, LensWrap::new(Slider::new(), AppState::value)),
4744
Padding::new(5.0, label),
4845
);
4946
col.add_child(either, 0.0);

druid/examples/slider.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@ fn build_widget() -> impl Widget<DemoState> {
3131
}
3232
});
3333
let mut row = Flex::row();
34-
let checkbox = LensWrap::new(Checkbox::new(), lenses::demo_state::double);
34+
let checkbox = LensWrap::new(Checkbox::new(), DemoState::double);
3535
let checkbox_label = Label::new("double the value");
3636
row.add_child(checkbox, 0.0);
3737
row.add_child(Padding::new(5.0, checkbox_label), 1.0);
3838

39-
let bar = LensWrap::new(ProgressBar::new(), lenses::demo_state::value);
40-
let slider = LensWrap::new(Slider::new(), lenses::demo_state::value);
39+
let bar = LensWrap::new(ProgressBar::new(), DemoState::value);
40+
let slider = LensWrap::new(Slider::new(), DemoState::value);
4141

4242
let button_1 = Button::sized(
4343
"increment ",

druid/examples/switch.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ struct DemoState {
2323
fn build_widget() -> impl Widget<DemoState> {
2424
let mut col = Flex::column();
2525
let mut row = Flex::row();
26-
let switch = LensWrap::new(Switch::new(), lenses::demo_state::value);
26+
let switch = LensWrap::new(Switch::new(), DemoState::value);
2727
let switch_label = Label::new("Setting label");
2828

2929
row.add_child(Padding::new(5.0, switch_label), 0.0);

0 commit comments

Comments
 (0)