pub struct Size {
pub width: f64,
pub height: f64,
}Expand description
A 2D size.
Fields§
§width: f64The width.
height: f64The height.
Implementations§
Source§impl Size
impl Size
Sourcepub const fn new(width: f64, height: f64) -> Size
pub const fn new(width: f64, height: f64) -> Size
Create a new Size with the provided width and height.
Examples found in repository?
More examples
48 fn layout(
49 &mut self,
50 _layout_ctx: &mut LayoutCtx,
51 bc: &BoxConstraints,
52 _data: &String,
53 _env: &Env,
54 ) -> Size {
55 // BoxConstraints are passed by the parent widget.
56 // This method can return any Size within those constraints:
57 // bc.constrain(my_size)
58 //
59 // To check if a dimension is infinite or not (e.g. scrolling):
60 // bc.is_width_bounded() / bc.is_height_bounded()
61 //
62 // bx.max() returns the maximum size of the widget. Be careful
63 // using this, since always make sure the widget is bounded.
64 // If bx.max() is used in a scrolling widget things will probably
65 // not work correctly.
66 if bc.is_width_bounded() && bc.is_height_bounded() {
67 bc.max()
68 } else {
69 let size = Size::new(100.0, 100.0);
70 bc.constrain(size)
71 }
72 }120 fn layout(
121 &mut self,
122 ctx: &mut druid::LayoutCtx,
123 bc: &druid::BoxConstraints,
124 data: &AppState,
125 env: &druid::Env,
126 ) -> druid::Size {
127 let mut interactable_area = Region::EMPTY;
128 let smaller_bc = BoxConstraints::new(
129 Size::new(0.0, 0.0),
130 Size::new(bc.max().width - 100.0, bc.max().height - 100.0),
131 );
132 let full_bc = BoxConstraints::new(Size::new(0.0, 0.0), bc.max());
133 let _label_size = self.info_label.layout(ctx, &smaller_bc, data, env);
134 let controls_size = self.controls.layout(ctx, &full_bc, data, env);
135
136 let text_origin_point = Point::new(50.0, 50.0 + controls_size.height);
137 self.info_label.set_origin(ctx, text_origin_point);
138 let controls_origin_point = Point::new(EXAMPLE_BORDER_SIZE, EXAMPLE_BORDER_SIZE);
139 self.controls.set_origin(ctx, controls_origin_point);
140
141 // Add side rects to clarify the dimensions of the window.
142 let left_rect = Rect::new(0.0, 0.0, EXAMPLE_BORDER_SIZE, bc.max().height);
143 let right_rect = Rect::new(
144 bc.max().width - EXAMPLE_BORDER_SIZE,
145 0.0,
146 bc.max().width,
147 bc.max().height,
148 );
149 let bottom_rect = Rect::new(
150 0.0,
151 bc.max().height - EXAMPLE_BORDER_SIZE,
152 bc.max().width,
153 bc.max().height,
154 );
155 interactable_area.add_rect(left_rect);
156 interactable_area.add_rect(right_rect);
157 interactable_area.add_rect(bottom_rect);
158 interactable_area.add_rect(self.info_label.layout_rect());
159 interactable_area.add_rect(self.controls.layout_rect());
160
161 if data.limit_input_region {
162 ctx.window().set_input_region(Some(interactable_area));
163 } else {
164 ctx.window().set_input_region(None);
165 }
166
167 bc.max()
168 }99 fn event(&mut self, child: &mut W, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) {
100 let wait_duration = Duration::from_millis(500);
101 let resched_dur = Duration::from_millis(50);
102 let cursor_size = Size::new(15., 15.);
103 let now = Instant::now();
104 let new_state = match &self.state {
105 TooltipState::Fresh => match event {
106 Event::MouseMove(me) if ctx.is_hot() => Some(TooltipState::Waiting {
107 last_move: now,
108 timer_expire: now + wait_duration,
109 token: ctx.request_timer(wait_duration),
110 position_in_window_coordinates: me.window_pos,
111 }),
112 _ => None,
113 },
114 TooltipState::Waiting {
115 last_move,
116 timer_expire,
117 token,
118 position_in_window_coordinates,
119 } => match event {
120 Event::MouseMove(me) if ctx.is_hot() => {
121 let (cur_token, cur_expire) = if *timer_expire - now < resched_dur {
122 (ctx.request_timer(wait_duration), now + wait_duration)
123 } else {
124 (*token, *timer_expire)
125 };
126 Some(TooltipState::Waiting {
127 last_move: now,
128 timer_expire: cur_expire,
129 token: cur_token,
130 position_in_window_coordinates: me.window_pos,
131 })
132 }
133 Event::Timer(tok) if tok == token => {
134 let deadline = *last_move + wait_duration;
135 ctx.set_handled();
136 if deadline > now {
137 let wait_for = deadline - now;
138 tracing::info!("Waiting another {:?}", wait_for);
139 Some(TooltipState::Waiting {
140 last_move: *last_move,
141 timer_expire: deadline,
142 token: ctx.request_timer(wait_for),
143 position_in_window_coordinates: *position_in_window_coordinates,
144 })
145 } else {
146 let tooltip_position_in_window_coordinates =
147 (position_in_window_coordinates.to_vec2() + cursor_size.to_vec2())
148 .to_point();
149 let win_id = ctx.new_sub_window(
150 WindowConfig::default()
151 .show_titlebar(false)
152 .window_size_policy(WindowSizePolicy::Content)
153 .set_level(WindowLevel::Tooltip(ctx.window().clone()))
154 .set_position(tooltip_position_in_window_coordinates),
155 Label::<()>::new(self.tip.clone()),
156 (),
157 env.clone(),
158 );
159 Some(TooltipState::Showing(win_id))
160 }
161 }
162 _ => None,
163 },
164 TooltipState::Showing(win_id) => {
165 match event {
166 Event::MouseMove(me) if !ctx.is_hot() => {
167 // TODO another timer on leaving
168 tracing::info!("Sending close window for {:?}", win_id);
169 ctx.submit_command(CLOSE_WINDOW.to(*win_id));
170 Some(TooltipState::Waiting {
171 last_move: now,
172 timer_expire: now + wait_duration,
173 token: ctx.request_timer(wait_duration),
174 position_in_window_coordinates: me.window_pos,
175 })
176 }
177 _ => None,
178 }
179 }
180 };
181
182 if let Some(state) = new_state {
183 self.state = state;
184 }
185
186 if !ctx.is_handled() {
187 child.event(ctx, event, data, env);
188 }
189 }
190
191 fn lifecycle(
192 &mut self,
193 child: &mut W,
194 ctx: &mut LifeCycleCtx,
195 event: &LifeCycle,
196 data: &T,
197 env: &Env,
198 ) {
199 if let LifeCycle::HotChanged(false) = event {
200 if let TooltipState::Showing(win_id) = self.state {
201 ctx.submit_command(CLOSE_WINDOW.to(win_id));
202 }
203 self.state = TooltipState::Fresh;
204 }
205 child.lifecycle(ctx, event, data, env)
206 }
207}
208
209struct DragWindowController {
210 init_pos: Option<Point>,
211 //dragging: bool
212}
213
214impl DragWindowController {
215 pub fn new() -> Self {
216 DragWindowController { init_pos: None }
217 }
218}
219
220impl<T, W: Widget<T>> Controller<T, W> for DragWindowController {
221 fn event(&mut self, child: &mut W, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) {
222 match event {
223 Event::MouseDown(me) if me.buttons.has_left() => {
224 ctx.set_active(true);
225 self.init_pos = Some(me.window_pos)
226 }
227 Event::MouseMove(me) if ctx.is_active() && me.buttons.has_left() => {
228 if let Some(init_pos) = self.init_pos {
229 let within_window_change = me.window_pos.to_vec2() - init_pos.to_vec2();
230 let old_pos = ctx.window().get_position();
231 let new_pos = old_pos + within_window_change;
232 tracing::info!(
233 "Drag {:?} ",
234 (
235 init_pos,
236 me.window_pos,
237 within_window_change,
238 old_pos,
239 new_pos
240 )
241 );
242 ctx.window().set_position(new_pos)
243 }
244 }
245 Event::MouseUp(_me) if ctx.is_active() => {
246 self.init_pos = None;
247 ctx.set_active(false)
248 }
249 _ => (),
250 }
251 child.event(ctx, event, data, env)
252 }
253}
254
255struct ScreenThing;
256
257impl Widget<()> for ScreenThing {
258 fn event(&mut self, _ctx: &mut EventCtx, _event: &Event, _data: &mut (), _env: &Env) {}
259
260 fn lifecycle(&mut self, _ctx: &mut LifeCycleCtx, _event: &LifeCycle, _data: &(), _env: &Env) {}
261
262 fn update(&mut self, _ctx: &mut UpdateCtx, _old_data: &(), _data: &(), _env: &Env) {}
263
264 fn layout(
265 &mut self,
266 _ctx: &mut LayoutCtx,
267 bc: &BoxConstraints,
268 _data: &(),
269 _env: &Env,
270 ) -> Size {
271 bc.constrain(Size::new(800.0, 600.0))
272 }
273
274 fn paint(&mut self, ctx: &mut PaintCtx, _data: &(), env: &Env) {
275 let sz = ctx.size();
276
277 let monitors = Screen::get_monitors();
278 let all = monitors
279 .iter()
280 .map(|x| x.virtual_rect())
281 .fold(Rect::ZERO, |s, r| r.union(s));
282 if all.width() > 0. && all.height() > 0. {
283 let trans = Affine::scale(f64::min(sz.width / all.width(), sz.height / all.height()))
284 * Affine::translate(all.origin().to_vec2()).inverse();
285 let font = env.get(theme::UI_FONT).family;
286
287 for (i, mon) in monitors.iter().enumerate() {
288 let vr = mon.virtual_rect();
289 let tr = trans.transform_rect_bbox(vr);
290 ctx.stroke(tr, &Color::WHITE, 1.0);
291
292 if let Ok(tl) = ctx
293 .text()
294 .new_text_layout(format!(
295 "{}:{}x{}@{},{}",
296 i,
297 vr.width(),
298 vr.height(),
299 vr.x0,
300 vr.y0
301 ))
302 .max_width(tr.width() - 5.)
303 .font(font.clone(), env.get(theme::TEXT_SIZE_NORMAL))
304 .text_color(Color::WHITE)
305 .build()
306 {
307 ctx.draw_text(&tl, tr.center() - tl.size().to_vec2() * 0.5);
308 }
309 }
310 }
311 }
312}
313
314struct CancelClose;
315
316impl<W: Widget<bool>> Controller<bool, W> for CancelClose {
317 fn event(
318 &mut self,
319 w: &mut W,
320 ctx: &mut EventCtx<'_, '_>,
321 event: &Event,
322 data: &mut bool,
323 env: &Env,
324 ) {
325 match (&data, event) {
326 (false, Event::WindowCloseRequested) => ctx.set_handled(),
327 _ => w.event(ctx, event, data, env),
328 }
329 }
330}
331
332fn build_root_widget() -> impl Widget<HelloState> {
333 let label = EnvScope::new(
334 |env, _t| env.set(theme::TEXT_COLOR, env.get(theme::PRIMARY_LIGHT)),
335 ControllerHost::new(
336 Label::new(|data: &HelloState, _env: &Env| {
337 format!("Hello {}! {} ", data.name, data.sub.my_stuff)
338 }),
339 TooltipController::new("Tips! Are good"),
340 ),
341 );
342 // a textbox that modifies `name`.
343 let textbox = TextBox::new()
344 .with_placeholder("Who are we greeting?")
345 .fix_width(TEXT_BOX_WIDTH)
346 .lens(HelloState::sub.then(SubState::my_stuff));
347
348 let button = Button::new("Make sub window")
349 .on_click(|ctx, data: &mut SubState, env| {
350 let tb = TextBox::new().lens(SubState::my_stuff);
351 let drag_thing = Label::new("Drag me").controller(DragWindowController::new());
352 let col = Flex::column().with_child(drag_thing).with_child(tb);
353
354 ctx.new_sub_window(
355 WindowConfig::default()
356 .show_titlebar(false)
357 .window_size(Size::new(100., 100.))
358 .set_level(WindowLevel::AppWindow),
359 col,
360 data.clone(),
361 env.clone(),
362 );
363 })
364 .center()
365 .lens(HelloState::sub);
366
367 let check_box =
368 ControllerHost::new(Checkbox::new("Closeable?"), CancelClose).lens(HelloState::closeable);
369 // arrange the two widgets vertically, with some padding
370 let layout = Flex::column()
371 .with_child(label)
372 .with_flex_child(ScreenThing.lens(Unit::default()).padding(5.), 1.)
373 .with_spacer(VERTICAL_WIDGET_SPACING)
374 .with_child(textbox)
375 .with_child(button)
376 .with_child(check_box);
377
378 // center the two widgets in the available space
379 Align::centered(layout)
380}Sourcepub fn max_side(self) -> f64
pub fn max_side(self) -> f64
Returns the max of width and height.
§Examples
use kurbo::Size;
let size = Size::new(-10.5, 42.0);
assert_eq!(size.max_side(), 42.0);Sourcepub fn min_side(self) -> f64
pub fn min_side(self) -> f64
Returns the min of width and height.
§Examples
use kurbo::Size;
let size = Size::new(-10.5, 42.0);
assert_eq!(size.min_side(), -10.5);Sourcepub fn is_empty(self) -> bool
pub fn is_empty(self) -> bool
Whether this size has zero area.
Note: a size with negative area is not considered empty.
Sourcepub fn clamp(self, min: Size, max: Size) -> Size
pub fn clamp(self, min: Size, max: Size) -> Size
Returns a new size bounded by min and max.
§Examples
use kurbo::Size;
let this = Size::new(0., 100.);
let min = Size::new(10., 10.,);
let max = Size::new(50., 50.);
assert_eq!(this.clamp(min, max), Size::new(10., 50.))Sourcepub const fn to_vec2(self) -> Vec2
pub const fn to_vec2(self) -> Vec2
Convert this size into a Vec2, with width mapped to x and height
mapped to y.
Examples found in repository?
99 fn event(&mut self, child: &mut W, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) {
100 let wait_duration = Duration::from_millis(500);
101 let resched_dur = Duration::from_millis(50);
102 let cursor_size = Size::new(15., 15.);
103 let now = Instant::now();
104 let new_state = match &self.state {
105 TooltipState::Fresh => match event {
106 Event::MouseMove(me) if ctx.is_hot() => Some(TooltipState::Waiting {
107 last_move: now,
108 timer_expire: now + wait_duration,
109 token: ctx.request_timer(wait_duration),
110 position_in_window_coordinates: me.window_pos,
111 }),
112 _ => None,
113 },
114 TooltipState::Waiting {
115 last_move,
116 timer_expire,
117 token,
118 position_in_window_coordinates,
119 } => match event {
120 Event::MouseMove(me) if ctx.is_hot() => {
121 let (cur_token, cur_expire) = if *timer_expire - now < resched_dur {
122 (ctx.request_timer(wait_duration), now + wait_duration)
123 } else {
124 (*token, *timer_expire)
125 };
126 Some(TooltipState::Waiting {
127 last_move: now,
128 timer_expire: cur_expire,
129 token: cur_token,
130 position_in_window_coordinates: me.window_pos,
131 })
132 }
133 Event::Timer(tok) if tok == token => {
134 let deadline = *last_move + wait_duration;
135 ctx.set_handled();
136 if deadline > now {
137 let wait_for = deadline - now;
138 tracing::info!("Waiting another {:?}", wait_for);
139 Some(TooltipState::Waiting {
140 last_move: *last_move,
141 timer_expire: deadline,
142 token: ctx.request_timer(wait_for),
143 position_in_window_coordinates: *position_in_window_coordinates,
144 })
145 } else {
146 let tooltip_position_in_window_coordinates =
147 (position_in_window_coordinates.to_vec2() + cursor_size.to_vec2())
148 .to_point();
149 let win_id = ctx.new_sub_window(
150 WindowConfig::default()
151 .show_titlebar(false)
152 .window_size_policy(WindowSizePolicy::Content)
153 .set_level(WindowLevel::Tooltip(ctx.window().clone()))
154 .set_position(tooltip_position_in_window_coordinates),
155 Label::<()>::new(self.tip.clone()),
156 (),
157 env.clone(),
158 );
159 Some(TooltipState::Showing(win_id))
160 }
161 }
162 _ => None,
163 },
164 TooltipState::Showing(win_id) => {
165 match event {
166 Event::MouseMove(me) if !ctx.is_hot() => {
167 // TODO another timer on leaving
168 tracing::info!("Sending close window for {:?}", win_id);
169 ctx.submit_command(CLOSE_WINDOW.to(*win_id));
170 Some(TooltipState::Waiting {
171 last_move: now,
172 timer_expire: now + wait_duration,
173 token: ctx.request_timer(wait_duration),
174 position_in_window_coordinates: me.window_pos,
175 })
176 }
177 _ => None,
178 }
179 }
180 };
181
182 if let Some(state) = new_state {
183 self.state = state;
184 }
185
186 if !ctx.is_handled() {
187 child.event(ctx, event, data, env);
188 }
189 }
190
191 fn lifecycle(
192 &mut self,
193 child: &mut W,
194 ctx: &mut LifeCycleCtx,
195 event: &LifeCycle,
196 data: &T,
197 env: &Env,
198 ) {
199 if let LifeCycle::HotChanged(false) = event {
200 if let TooltipState::Showing(win_id) = self.state {
201 ctx.submit_command(CLOSE_WINDOW.to(win_id));
202 }
203 self.state = TooltipState::Fresh;
204 }
205 child.lifecycle(ctx, event, data, env)
206 }
207}
208
209struct DragWindowController {
210 init_pos: Option<Point>,
211 //dragging: bool
212}
213
214impl DragWindowController {
215 pub fn new() -> Self {
216 DragWindowController { init_pos: None }
217 }
218}
219
220impl<T, W: Widget<T>> Controller<T, W> for DragWindowController {
221 fn event(&mut self, child: &mut W, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) {
222 match event {
223 Event::MouseDown(me) if me.buttons.has_left() => {
224 ctx.set_active(true);
225 self.init_pos = Some(me.window_pos)
226 }
227 Event::MouseMove(me) if ctx.is_active() && me.buttons.has_left() => {
228 if let Some(init_pos) = self.init_pos {
229 let within_window_change = me.window_pos.to_vec2() - init_pos.to_vec2();
230 let old_pos = ctx.window().get_position();
231 let new_pos = old_pos + within_window_change;
232 tracing::info!(
233 "Drag {:?} ",
234 (
235 init_pos,
236 me.window_pos,
237 within_window_change,
238 old_pos,
239 new_pos
240 )
241 );
242 ctx.window().set_position(new_pos)
243 }
244 }
245 Event::MouseUp(_me) if ctx.is_active() => {
246 self.init_pos = None;
247 ctx.set_active(false)
248 }
249 _ => (),
250 }
251 child.event(ctx, event, data, env)
252 }
253}
254
255struct ScreenThing;
256
257impl Widget<()> for ScreenThing {
258 fn event(&mut self, _ctx: &mut EventCtx, _event: &Event, _data: &mut (), _env: &Env) {}
259
260 fn lifecycle(&mut self, _ctx: &mut LifeCycleCtx, _event: &LifeCycle, _data: &(), _env: &Env) {}
261
262 fn update(&mut self, _ctx: &mut UpdateCtx, _old_data: &(), _data: &(), _env: &Env) {}
263
264 fn layout(
265 &mut self,
266 _ctx: &mut LayoutCtx,
267 bc: &BoxConstraints,
268 _data: &(),
269 _env: &Env,
270 ) -> Size {
271 bc.constrain(Size::new(800.0, 600.0))
272 }
273
274 fn paint(&mut self, ctx: &mut PaintCtx, _data: &(), env: &Env) {
275 let sz = ctx.size();
276
277 let monitors = Screen::get_monitors();
278 let all = monitors
279 .iter()
280 .map(|x| x.virtual_rect())
281 .fold(Rect::ZERO, |s, r| r.union(s));
282 if all.width() > 0. && all.height() > 0. {
283 let trans = Affine::scale(f64::min(sz.width / all.width(), sz.height / all.height()))
284 * Affine::translate(all.origin().to_vec2()).inverse();
285 let font = env.get(theme::UI_FONT).family;
286
287 for (i, mon) in monitors.iter().enumerate() {
288 let vr = mon.virtual_rect();
289 let tr = trans.transform_rect_bbox(vr);
290 ctx.stroke(tr, &Color::WHITE, 1.0);
291
292 if let Ok(tl) = ctx
293 .text()
294 .new_text_layout(format!(
295 "{}:{}x{}@{},{}",
296 i,
297 vr.width(),
298 vr.height(),
299 vr.x0,
300 vr.y0
301 ))
302 .max_width(tr.width() - 5.)
303 .font(font.clone(), env.get(theme::TEXT_SIZE_NORMAL))
304 .text_color(Color::WHITE)
305 .build()
306 {
307 ctx.draw_text(&tl, tr.center() - tl.size().to_vec2() * 0.5);
308 }
309 }
310 }
311 }Sourcepub fn round(self) -> Size
pub fn round(self) -> Size
Returns a new Size,
with width and height rounded to the nearest integer.
§Examples
use kurbo::Size;
let size_pos = Size::new(3.3, 3.6).round();
assert_eq!(size_pos.width, 3.0);
assert_eq!(size_pos.height, 4.0);
let size_neg = Size::new(-3.3, -3.6).round();
assert_eq!(size_neg.width, -3.0);
assert_eq!(size_neg.height, -4.0);Sourcepub fn ceil(self) -> Size
pub fn ceil(self) -> Size
Returns a new Size,
with width and height rounded up to the nearest integer,
unless they are already an integer.
§Examples
use kurbo::Size;
let size_pos = Size::new(3.3, 3.6).ceil();
assert_eq!(size_pos.width, 4.0);
assert_eq!(size_pos.height, 4.0);
let size_neg = Size::new(-3.3, -3.6).ceil();
assert_eq!(size_neg.width, -3.0);
assert_eq!(size_neg.height, -3.0);Sourcepub fn floor(self) -> Size
pub fn floor(self) -> Size
Returns a new Size,
with width and height rounded down to the nearest integer,
unless they are already an integer.
§Examples
use kurbo::Size;
let size_pos = Size::new(3.3, 3.6).floor();
assert_eq!(size_pos.width, 3.0);
assert_eq!(size_pos.height, 3.0);
let size_neg = Size::new(-3.3, -3.6).floor();
assert_eq!(size_neg.width, -4.0);
assert_eq!(size_neg.height, -4.0);Sourcepub fn expand(self) -> Size
pub fn expand(self) -> Size
Returns a new Size,
with width and height rounded away from zero to the nearest integer,
unless they are already an integer.
§Examples
use kurbo::Size;
let size_pos = Size::new(3.3, 3.6).expand();
assert_eq!(size_pos.width, 4.0);
assert_eq!(size_pos.height, 4.0);
let size_neg = Size::new(-3.3, -3.6).expand();
assert_eq!(size_neg.width, -4.0);
assert_eq!(size_neg.height, -4.0);Sourcepub fn trunc(self) -> Size
pub fn trunc(self) -> Size
Returns a new Size,
with width and height rounded down towards zero the nearest integer,
unless they are already an integer.
§Examples
use kurbo::Size;
let size_pos = Size::new(3.3, 3.6).trunc();
assert_eq!(size_pos.width, 3.0);
assert_eq!(size_pos.height, 3.0);
let size_neg = Size::new(-3.3, -3.6).trunc();
assert_eq!(size_neg.width, -3.0);
assert_eq!(size_neg.height, -3.0);Sourcepub fn aspect_ratio(self) -> f64
pub fn aspect_ratio(self) -> f64
Returns the aspect ratio of a rectangle with the given size.
If the width is 0, the output will be sign(self.height) * infinity. If The width and
height are 0, then the output will be NaN.
Sourcepub const fn to_rect(self) -> Rect
pub const fn to_rect(self) -> Rect
Convert this Size into a Rect with origin (0.0, 0.0).
Examples found in repository?
137 fn paint(&mut self, ctx: &mut PaintCtx, data: &Vector<Circle>, _env: &Env) {
138 ctx.with_save(|ctx| {
139 let rect = ctx.size().to_rect();
140 ctx.clip(rect);
141 ctx.fill(rect, &Color::WHITE);
142 let now = Instant::now();
143 for c in data {
144 let color =
145 Color::BLACK.with_alpha(now.duration_since(c.time).as_secs_f64().cos().abs());
146 ctx.fill(kurbo::Circle::new(c.pos, RADIUS), &color);
147 }
148 });
149 }More examples
121fn op_button_label(op: char, label: String) -> impl Widget<CalcState> {
122 let painter = Painter::new(|ctx, _, env| {
123 let bounds = ctx.size().to_rect();
124
125 ctx.fill(bounds, &env.get(theme::PRIMARY_DARK));
126
127 if ctx.is_hot() {
128 ctx.stroke(bounds.inset(-0.5), &Color::WHITE, 1.0);
129 }
130
131 if ctx.is_active() {
132 ctx.fill(bounds, &env.get(theme::PRIMARY_LIGHT));
133 }
134 });
135
136 Label::new(label)
137 .with_text_size(24.)
138 .center()
139 .background(painter)
140 .expand()
141 .on_click(move |_ctx, data: &mut CalcState, _env| data.op(op))
142}
143
144fn op_button(op: char) -> impl Widget<CalcState> {
145 op_button_label(op, op.to_string())
146}
147
148fn digit_button(digit: u8) -> impl Widget<CalcState> {
149 let painter = Painter::new(|ctx, _, env| {
150 let bounds = ctx.size().to_rect();
151
152 ctx.fill(bounds, &env.get(theme::BACKGROUND_LIGHT));
153
154 if ctx.is_hot() {
155 ctx.stroke(bounds.inset(-0.5), &Color::WHITE, 1.0);
156 }
157
158 if ctx.is_active() {
159 ctx.fill(bounds, &Color::rgb8(0x71, 0x71, 0x71));
160 }
161 });
162
163 Label::new(format!("{digit}"))
164 .with_text_size(24.)
165 .center()
166 .background(painter)
167 .expand()
168 .on_click(move |_ctx, data: &mut CalcState, _env| data.digit(digit))
169}62fn build_root_widget() -> impl Widget<HelloState> {
63 // Draw red circle, and two semi-transparent rectangles
64 let circle_and_rects = Painter::new(|ctx, _data, _env| {
65 let boundaries = ctx.size().to_rect();
66 let center = (boundaries.width() / 2., boundaries.height() / 2.);
67 let circle = Circle::new(center, center.0.min(center.1));
68 ctx.fill(circle, &Color::RED);
69
70 let rect1 = Rect::new(0., 0., boundaries.width() / 2., boundaries.height() / 2.);
71 ctx.fill(rect1, &Color::rgba8(0x0, 0xff, 0, 125));
72
73 let rect2 = Rect::new(
74 boundaries.width() / 2.,
75 boundaries.height() / 2.,
76 boundaries.width(),
77 boundaries.height(),
78 );
79 ctx.fill(rect2, &Color::rgba8(0x0, 0x0, 0xff, 125));
80 });
81
82 // This textbox modifies the label, idea here is to test that the background
83 // invalidation works when you type to the textbox
84 let textbox = TextBox::new()
85 .with_placeholder("Type to test clearing")
86 .with_text_size(18.0)
87 .lens(HelloState::name)
88 .fix_width(250.);
89
90 let label = Label::new(|data: &HelloState, _env: &Env| {
91 if data.name.is_empty() {
92 "Text: ".to_string()
93 } else {
94 format!("Text: {}!", data.name)
95 }
96 })
97 .with_text_color(Color::RED)
98 .with_text_size(32.0);
99
100 Flex::column()
101 .with_flex_child(circle_and_rects.expand().controller(DragController), 10.0)
102 .with_spacer(4.0)
103 .with_child(textbox)
104 .with_spacer(4.0)
105 .with_child(label)
106}31fn build_app() -> impl Widget<()> {
32 let gradient = LinearGradient::new(
33 UnitPoint::TOP_LEFT,
34 UnitPoint::BOTTOM_RIGHT,
35 (DARKER_GREY, LIGHTER_GREY),
36 );
37
38 // a custom background
39 let polka_dots = Painter::new(|ctx, _, _| {
40 let bounds = ctx.size().to_rect();
41 let dot_diam = bounds.width().max(bounds.height()) / 20.;
42 let dot_spacing = dot_diam * 1.8;
43 for y in 0..((bounds.height() / dot_diam).ceil() as usize) {
44 for x in 0..((bounds.width() / dot_diam).ceil() as usize) {
45 let x_offset = (y % 2) as f64 * (dot_spacing / 2.0);
46 let x = x as f64 * dot_spacing + x_offset;
47 let y = y as f64 * dot_spacing;
48 let circ = Circle::new((x, y), dot_diam / 2.0);
49 let purp = Color::rgb(1.0, 0.22, 0.76);
50 ctx.fill(circ, &purp);
51 }
52 }
53 });
54
55 Flex::column()
56 .with_flex_child(
57 Flex::row()
58 .with_flex_child(
59 Label::new("top left")
60 .center()
61 .border(DARK_GREY, 4.0)
62 .padding(10.0),
63 1.0,
64 )
65 .with_flex_child(
66 Label::new("top right")
67 .center()
68 .background(DARK_GREY)
69 .padding(10.0),
70 1.0,
71 ),
72 1.0,
73 )
74 .with_flex_child(
75 Flex::row()
76 .with_flex_child(
77 Label::new("bottom left")
78 .center()
79 .background(gradient)
80 .rounded(10.0)
81 .padding(10.0),
82 1.0,
83 )
84 .with_flex_child(
85 Label::new("bottom right")
86 .center()
87 .border(LIGHTER_GREY, 4.0)
88 .background(polka_dots)
89 .rounded(10.0)
90 .padding(10.0),
91 1.0,
92 ),
93 1.0,
94 )
95}72fn ui_builder() -> impl Widget<AppData> {
73 let my_painter = Painter::new(|ctx, _, _| {
74 let bounds = ctx.size().to_rect();
75 if ctx.is_hot() {
76 ctx.fill(bounds, &Color::rgba8(0, 0, 0, 128));
77 }
78
79 if ctx.is_active() {
80 ctx.stroke(bounds, &Color::WHITE, 2.0);
81 }
82 });
83
84 // This is Druid's default text style.
85 // It's set by theme::LABEL_COLOR and theme::UI_FONT
86 let label =
87 Label::new(|data: &String, _env: &_| format!("Default: {data}")).lens(AppData::text);
88
89 // The text_color, text_size, and font builder methods can override the
90 // defaults provided by the theme by passing in a Key or a concrete value.
91 //
92 // In this example, text_color receives a Key from the theme, text_size
93 // gets a custom key which we set with the env_scope wrapper, and the
94 // default font key (theme::FONT_NAME) is overridden in the env_scope
95 // wrapper. (Like text_color and text_size, the font can be set using the
96 // with_font builder method, but overriding here makes it easy to fall back
97 // to the default font)
98 let styled_label = Label::new(|data: &AppData, _env: &_| format!("{data}"))
99 .with_text_color(theme::PRIMARY_LIGHT)
100 .with_font(MY_CUSTOM_FONT)
101 .background(my_painter)
102 .on_click(|_, data, _| {
103 data.size *= 1.1;
104 })
105 .env_scope(|env: &mut druid::Env, data: &AppData| {
106 let new_font = if data.mono {
107 FontDescriptor::new(FontFamily::MONOSPACE)
108 } else {
109 FontDescriptor::new(FontFamily::SYSTEM_UI)
110 }
111 .with_size(data.size);
112 env.set(MY_CUSTOM_FONT, new_font);
113 });
114
115 let labels = Scroll::new(
116 Flex::column()
117 .cross_axis_alignment(CrossAxisAlignment::Start)
118 .with_child(label)
119 .with_default_spacer()
120 .with_child(styled_label),
121 )
122 .expand_height()
123 .fix_width(COLUMN_WIDTH);
124
125 let stepper = Stepper::new()
126 .with_range(0.0, 100.0)
127 .with_step(1.0)
128 .with_wraparound(false)
129 .lens(AppData::size);
130
131 // TODO: Replace Parse usage with TextBox::with_formatter
132 #[allow(deprecated)]
133 let stepper_textbox = LensWrap::new(
134 Parse::new(TextBox::new()),
135 AppData::size.map(|x| Some(*x), |x, y| *x = y.unwrap_or(24.0)),
136 );
137
138 let mono_checkbox = Checkbox::new("Monospace").lens(AppData::mono);
139 let stepper_row = Flex::row()
140 .with_child(stepper_textbox)
141 .with_child(stepper)
142 .with_default_spacer()
143 .with_child(mono_checkbox);
144
145 let input = TextBox::multiline()
146 .with_placeholder("Your sample text here :)")
147 .fix_width(COLUMN_WIDTH)
148 .fix_height(140.0)
149 .lens(AppData::text);
150
151 Flex::column()
152 .main_axis_alignment(MainAxisAlignment::Center)
153 .with_default_spacer()
154 .with_flex_child(labels, 1.0)
155 .with_default_spacer()
156 .with_child(input)
157 .with_default_spacer()
158 .with_child(stepper_row)
159 .with_default_spacer()
160}77 fn paint(&mut self, ctx: &mut PaintCtx, data: &String, env: &Env) {
78 // Clear the whole widget with the color of your choice
79 // (ctx.size() returns the size of the layout rect we're painting in)
80 // Note: ctx also has a `clear` method, but that clears the whole context,
81 // and we only want to clear this widget's area.
82 let size = ctx.size();
83 let rect = size.to_rect();
84 ctx.fill(rect, &Color::WHITE);
85
86 // We can paint with a Z index, this indicates that this code will be run
87 // after the rest of the painting. Painting with z-index is done in order,
88 // so first everything with z-index 1 is painted and then with z-index 2 etc.
89 // As you can see this(red) curve is drawn on top of the green curve
90 ctx.paint_with_z_index(1, move |ctx| {
91 let mut path = BezPath::new();
92 path.move_to((0.0, size.height));
93 path.quad_to((40.0, 50.0), (size.width, 0.0));
94 // Create a color
95 let stroke_color = Color::rgb8(128, 0, 0);
96 // Stroke the path with thickness 1.0
97 ctx.stroke(path, &stroke_color, 5.0);
98 });
99
100 // Create an arbitrary bezier path
101 let mut path = BezPath::new();
102 path.move_to(Point::ORIGIN);
103 path.quad_to((40.0, 50.0), (size.width, size.height));
104 // Create a color
105 let stroke_color = Color::rgb8(0, 128, 0);
106 // Stroke the path with thickness 5.0
107 ctx.stroke(path, &stroke_color, 5.0);
108
109 // Rectangles: the path for practical people
110 let rect = Rect::from_origin_size((10.0, 10.0), (100.0, 100.0));
111 // Note the Color:rgba8 which includes an alpha channel (7F in this case)
112 let fill_color = Color::rgba8(0x00, 0x00, 0x00, 0x7F);
113 ctx.fill(rect, &fill_color);
114
115 // Text is easy; in real use TextLayout should either be stored in the
116 // widget and reused, or a label child widget to manage it all.
117 // This is one way of doing it, you can also use a builder-style way.
118 let mut layout = TextLayout::<String>::from_text(data);
119 layout.set_font(FontDescriptor::new(FontFamily::SERIF).with_size(24.0));
120 layout.set_text_color(fill_color);
121 layout.rebuild_if_needed(ctx.text(), env);
122
123 // Let's rotate our text slightly. First we save our current (default) context:
124 ctx.with_save(|ctx| {
125 // Now we can rotate the context (or set a clip path, for instance):
126 // This makes it so that anything drawn after this (in the closure) is
127 // transformed.
128 // The transformation is in radians, but be aware it transforms the canvas,
129 // not just the part you are drawing. So we draw at (80.0, 40.0) on the rotated
130 // canvas, this is NOT the same position as (80.0, 40.0) on the original canvas.
131 ctx.transform(Affine::rotate(std::f64::consts::FRAC_PI_4));
132 layout.draw(ctx, (80.0, 40.0));
133 });
134 // When we exit with_save, the original context's rotation is restored
135
136 // This is the builder-style way of drawing text.
137 let text = ctx.text();
138 let layout = text
139 .new_text_layout(data.clone())
140 .font(FontFamily::SERIF, 24.0)
141 .text_color(Color::rgb8(128, 0, 0))
142 .build()
143 .unwrap();
144 ctx.draw_text(&layout, (100.0, 25.0));
145
146 // Let's burn some CPU to make a (partially transparent) image buffer
147 let image_data = make_image_data(256, 256);
148 let image = ctx
149 .make_image(256, 256, &image_data, ImageFormat::RgbaSeparate)
150 .unwrap();
151 // The image is automatically scaled to fit the rect you pass to draw_image
152 ctx.draw_image(&image, size.to_rect(), InterpolationMode::Bilinear);
153 }Sourcepub fn to_rounded_rect(self, radii: impl Into<RoundedRectRadii>) -> RoundedRect
pub fn to_rounded_rect(self, radii: impl Into<RoundedRectRadii>) -> RoundedRect
Convert this Size into a RoundedRect with origin (0.0, 0.0) and
the provided corner radius.
Trait Implementations§
Source§impl AddAssign for Size
impl AddAssign for Size
Source§fn add_assign(&mut self, other: Size)
fn add_assign(&mut self, other: Size)
+= operation. Read moreSource§impl DivAssign<f64> for Size
impl DivAssign<f64> for Size
Source§fn div_assign(&mut self, other: f64)
fn div_assign(&mut self, other: f64)
/= operation. Read moreSource§impl MulAssign<f64> for Size
impl MulAssign<f64> for Size
Source§fn mul_assign(&mut self, other: f64)
fn mul_assign(&mut self, other: f64)
*= operation. Read moreSource§impl Scalable for Size
impl Scalable for Size
Source§impl SubAssign for Size
impl SubAssign for Size
Source§fn sub_assign(&mut self, other: Size)
fn sub_assign(&mut self, other: Size)
-= operation. Read moreSource§impl ValueType for Size
impl ValueType for Size
Source§fn try_from_value(value: &Value) -> Result<Self, ValueTypeError>
fn try_from_value(value: &Value) -> Result<Self, ValueTypeError>
Value into this type.