pub struct EventCtx<'a, 'b> { /* private fields */ }
Expand description
A mutable context provided to event handling methods of widgets.
Widgets should call request_paint
whenever an event causes a change
in the widget’s appearance, to schedule a repaint.
Implementations§
source§impl EventCtx<'_, '_>
impl EventCtx<'_, '_>
sourcepub fn window(&self) -> &WindowHandle
pub fn window(&self) -> &WindowHandle
Returns a reference to the current WindowHandle
.
Examples found in repository?
34 35 36 37 38 39 40 41 42 43 44 45
fn event(
&mut self,
_child: &mut W,
ctx: &mut EventCtx,
event: &Event,
_data: &mut T,
_env: &Env,
) {
if let Event::MouseMove(_) = event {
ctx.window().handle_titlebar(true);
}
}
More examples
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 77 78 79 80 81 82 83
pub fn new() -> Self {
let info_label = Label::new(INFO_TEXT)
.with_line_break_mode(LineBreaking::WordWrap)
.padding(20.0)
.background(Color::rgba(0.2, 0.2, 0.2, 1.0));
let toggle_input_region = Button::new("Toggle Input Region")
.on_click(|ctx, data: &mut bool, _: &Env| {
*data = !*data;
tracing::debug!("Setting input region toggle to: {}", *data);
ctx.request_layout();
})
.lens(AppState::limit_input_region);
let toggle_titlebar = Button::new("Toggle TitleBar")
.on_click(|ctx, data: &mut bool, _: &Env| {
*data = !*data;
tracing::debug!("Setting titlebar visibility to: {}", *data);
ctx.window().show_titlebar(*data);
ctx.request_layout();
})
.lens(AppState::show_titlebar);
let toggle_always_on_top = Button::new("Toggle Always On Top")
.on_click(|ctx, data: &mut bool, _: &Env| {
*data = !*data;
tracing::debug!("Setting always on top to: {}", *data);
ctx.window().set_always_on_top(*data);
})
.lens(AppState::always_on_top);
let controls_flex = Flex::row()
.with_child(toggle_input_region)
.with_child(toggle_titlebar)
.with_child(toggle_always_on_top);
Self {
info_label: WidgetPod::new(info_label),
controls: WidgetPod::new(controls_flex),
}
}
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 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
fn event(&mut self, child: &mut W, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) {
let wait_duration = Duration::from_millis(500);
let resched_dur = Duration::from_millis(50);
let cursor_size = Size::new(15., 15.);
let now = Instant::now();
let new_state = match &self.state {
TooltipState::Fresh => match event {
Event::MouseMove(me) if ctx.is_hot() => Some(TooltipState::Waiting {
last_move: now,
timer_expire: now + wait_duration,
token: ctx.request_timer(wait_duration),
position_in_window_coordinates: me.window_pos,
}),
_ => None,
},
TooltipState::Waiting {
last_move,
timer_expire,
token,
position_in_window_coordinates,
} => match event {
Event::MouseMove(me) if ctx.is_hot() => {
let (cur_token, cur_expire) = if *timer_expire - now < resched_dur {
(ctx.request_timer(wait_duration), now + wait_duration)
} else {
(*token, *timer_expire)
};
Some(TooltipState::Waiting {
last_move: now,
timer_expire: cur_expire,
token: cur_token,
position_in_window_coordinates: me.window_pos,
})
}
Event::Timer(tok) if tok == token => {
let deadline = *last_move + wait_duration;
ctx.set_handled();
if deadline > now {
let wait_for = deadline - now;
tracing::info!("Waiting another {:?}", wait_for);
Some(TooltipState::Waiting {
last_move: *last_move,
timer_expire: deadline,
token: ctx.request_timer(wait_for),
position_in_window_coordinates: *position_in_window_coordinates,
})
} else {
let tooltip_position_in_window_coordinates =
(position_in_window_coordinates.to_vec2() + cursor_size.to_vec2())
.to_point();
let win_id = ctx.new_sub_window(
WindowConfig::default()
.show_titlebar(false)
.window_size_policy(WindowSizePolicy::Content)
.set_level(WindowLevel::Tooltip(ctx.window().clone()))
.set_position(tooltip_position_in_window_coordinates),
Label::<()>::new(self.tip.clone()),
(),
env.clone(),
);
Some(TooltipState::Showing(win_id))
}
}
_ => None,
},
TooltipState::Showing(win_id) => {
match event {
Event::MouseMove(me) if !ctx.is_hot() => {
// TODO another timer on leaving
tracing::info!("Sending close window for {:?}", win_id);
ctx.submit_command(CLOSE_WINDOW.to(*win_id));
Some(TooltipState::Waiting {
last_move: now,
timer_expire: now + wait_duration,
token: ctx.request_timer(wait_duration),
position_in_window_coordinates: me.window_pos,
})
}
_ => None,
}
}
};
if let Some(state) = new_state {
self.state = state;
}
if !ctx.is_handled() {
child.event(ctx, event, data, env);
}
}
fn lifecycle(
&mut self,
child: &mut W,
ctx: &mut LifeCycleCtx,
event: &LifeCycle,
data: &T,
env: &Env,
) {
if let LifeCycle::HotChanged(false) = event {
if let TooltipState::Showing(win_id) = self.state {
ctx.submit_command(CLOSE_WINDOW.to(win_id));
}
self.state = TooltipState::Fresh;
}
child.lifecycle(ctx, event, data, env)
}
}
struct DragWindowController {
init_pos: Option<Point>,
//dragging: bool
}
impl DragWindowController {
pub fn new() -> Self {
DragWindowController { init_pos: None }
}
}
impl<T, W: Widget<T>> Controller<T, W> for DragWindowController {
fn event(&mut self, child: &mut W, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) {
match event {
Event::MouseDown(me) if me.buttons.has_left() => {
ctx.set_active(true);
self.init_pos = Some(me.window_pos)
}
Event::MouseMove(me) if ctx.is_active() && me.buttons.has_left() => {
if let Some(init_pos) = self.init_pos {
let within_window_change = me.window_pos.to_vec2() - init_pos.to_vec2();
let old_pos = ctx.window().get_position();
let new_pos = old_pos + within_window_change;
tracing::info!(
"Drag {:?} ",
(
init_pos,
me.window_pos,
within_window_change,
old_pos,
new_pos
)
);
ctx.window().set_position(new_pos)
}
}
Event::MouseUp(_me) if ctx.is_active() => {
self.init_pos = None;
ctx.set_active(false)
}
_ => (),
}
child.event(ctx, event, data, env)
}
source§impl EventCtx<'_, '_>
impl EventCtx<'_, '_>
sourcepub fn size(&self) -> Size
pub fn size(&self) -> Size
The layout size.
This is the layout size as ultimately determined by the parent container, on the previous layout pass.
Generally it will be the same as the size returned by the child widget’s
layout
method.
Examples found in repository?
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut u32, env: &Env) {
match event {
Event::WindowConnected => {
// Start the timer when the application launches
self.timer_id = ctx.request_timer(TIMER_INTERVAL);
}
Event::Timer(id) => {
if *id == self.timer_id {
self.adjust_box_pos(ctx.size());
ctx.request_layout();
self.timer_id = ctx.request_timer(TIMER_INTERVAL);
}
}
_ => (),
}
self.simple_box.event(ctx, event, data, env);
}
sourcepub fn window_origin(&self) -> Point
pub fn window_origin(&self) -> Point
The origin of the widget in window coordinates, relative to the top left corner of the content area.
sourcepub fn to_window(&self, widget_point: Point) -> Point
pub fn to_window(&self, widget_point: Point) -> Point
Convert a point from the widget’s coordinate space to the window’s.
The returned point is relative to the content area; it excludes window chrome.
sourcepub fn to_screen(&self, widget_point: Point) -> Point
pub fn to_screen(&self, widget_point: Point) -> Point
Convert a point from the widget’s coordinate space to the screen’s.
See the Screen
module
sourcepub fn is_hot(&self) -> bool
pub fn is_hot(&self) -> bool
Query the “hot” state of the widget.
See WidgetPod::is_hot
for additional information.
Examples found in repository?
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 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
fn event(&mut self, child: &mut W, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) {
let wait_duration = Duration::from_millis(500);
let resched_dur = Duration::from_millis(50);
let cursor_size = Size::new(15., 15.);
let now = Instant::now();
let new_state = match &self.state {
TooltipState::Fresh => match event {
Event::MouseMove(me) if ctx.is_hot() => Some(TooltipState::Waiting {
last_move: now,
timer_expire: now + wait_duration,
token: ctx.request_timer(wait_duration),
position_in_window_coordinates: me.window_pos,
}),
_ => None,
},
TooltipState::Waiting {
last_move,
timer_expire,
token,
position_in_window_coordinates,
} => match event {
Event::MouseMove(me) if ctx.is_hot() => {
let (cur_token, cur_expire) = if *timer_expire - now < resched_dur {
(ctx.request_timer(wait_duration), now + wait_duration)
} else {
(*token, *timer_expire)
};
Some(TooltipState::Waiting {
last_move: now,
timer_expire: cur_expire,
token: cur_token,
position_in_window_coordinates: me.window_pos,
})
}
Event::Timer(tok) if tok == token => {
let deadline = *last_move + wait_duration;
ctx.set_handled();
if deadline > now {
let wait_for = deadline - now;
tracing::info!("Waiting another {:?}", wait_for);
Some(TooltipState::Waiting {
last_move: *last_move,
timer_expire: deadline,
token: ctx.request_timer(wait_for),
position_in_window_coordinates: *position_in_window_coordinates,
})
} else {
let tooltip_position_in_window_coordinates =
(position_in_window_coordinates.to_vec2() + cursor_size.to_vec2())
.to_point();
let win_id = ctx.new_sub_window(
WindowConfig::default()
.show_titlebar(false)
.window_size_policy(WindowSizePolicy::Content)
.set_level(WindowLevel::Tooltip(ctx.window().clone()))
.set_position(tooltip_position_in_window_coordinates),
Label::<()>::new(self.tip.clone()),
(),
env.clone(),
);
Some(TooltipState::Showing(win_id))
}
}
_ => None,
},
TooltipState::Showing(win_id) => {
match event {
Event::MouseMove(me) if !ctx.is_hot() => {
// TODO another timer on leaving
tracing::info!("Sending close window for {:?}", win_id);
ctx.submit_command(CLOSE_WINDOW.to(*win_id));
Some(TooltipState::Waiting {
last_move: now,
timer_expire: now + wait_duration,
token: ctx.request_timer(wait_duration),
position_in_window_coordinates: me.window_pos,
})
}
_ => None,
}
}
};
if let Some(state) = new_state {
self.state = state;
}
if !ctx.is_handled() {
child.event(ctx, event, data, env);
}
}
sourcepub fn is_active(&self) -> bool
pub fn is_active(&self) -> bool
Query the “active” state of the widget.
See WidgetPod::is_active
for additional information.
Examples found in repository?
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
fn event(&mut self, child: &mut W, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) {
match event {
Event::MouseDown(me) if me.buttons.has_left() => {
ctx.set_active(true);
self.init_pos = Some(me.window_pos)
}
Event::MouseMove(me) if ctx.is_active() && me.buttons.has_left() => {
if let Some(init_pos) = self.init_pos {
let within_window_change = me.window_pos.to_vec2() - init_pos.to_vec2();
let old_pos = ctx.window().get_position();
let new_pos = old_pos + within_window_change;
tracing::info!(
"Drag {:?} ",
(
init_pos,
me.window_pos,
within_window_change,
old_pos,
new_pos
)
);
ctx.window().set_position(new_pos)
}
}
Event::MouseUp(_me) if ctx.is_active() => {
self.init_pos = None;
ctx.set_active(false)
}
_ => (),
}
child.event(ctx, event, data, env)
}
sourcepub fn is_focused(&self) -> bool
pub fn is_focused(&self) -> bool
The focus status of a widget.
Returns true
if this specific widget is focused.
To check if any descendants are focused use has_focus
.
Focus means that the widget receives keyboard events.
A widget can request focus using the request_focus
method.
It’s also possible to register for automatic focus via register_for_focus
.
If a widget gains or loses focus it will get a LifeCycle::FocusChanged
event.
Only one widget at a time is focused. However due to the way events are routed, all ancestors of that widget will also receive keyboard events.
sourcepub fn has_focus(&self) -> bool
pub fn has_focus(&self) -> bool
The (tree) focus status of a widget.
Returns true
if either this specific widget or any one of its descendants is focused.
To check if only this specific widget is focused use is_focused
,
sourcepub fn is_disabled(&self) -> bool
pub fn is_disabled(&self) -> bool
The disabled state of a widget.
Returns true
if this widget or any of its ancestors is explicitly disabled.
To make this widget explicitly disabled use set_disabled
.
Disabled means that this widget should not change the state of the application. What
that means is not entirely clear but in any it should not change its data. Therefore
others can use this as a safety mechanism to prevent the application from entering an
illegal state.
For an example the decrease button of a counter of type usize
should be disabled if the
value is 0
.
source§impl EventCtx<'_, '_>
impl EventCtx<'_, '_>
sourcepub fn set_cursor(&mut self, cursor: &Cursor)
pub fn set_cursor(&mut self, cursor: &Cursor)
Set the cursor icon.
This setting will be retained until clear_cursor
is called, but it will only take
effect when this widget is either hot
or active
. If a child widget also sets a
cursor, the child widget’s cursor will take precedence. (If that isn’t what you want, use
override_cursor
instead.)
sourcepub fn override_cursor(&mut self, cursor: &Cursor)
pub fn override_cursor(&mut self, cursor: &Cursor)
Override the cursor icon.
This setting will be retained until clear_cursor
is called, but it will only take
effect when this widget is either hot
or active
. This will override the cursor
preferences of a child widget. (If that isn’t what you want, use set_cursor
instead.)
sourcepub fn clear_cursor(&mut self)
pub fn clear_cursor(&mut self)
Clear the cursor icon.
This undoes the effect of set_cursor
and override_cursor
.
source§impl EventCtx<'_, '_>
impl EventCtx<'_, '_>
sourcepub fn view_context_changed(&mut self)
pub fn view_context_changed(&mut self)
Indicate that your ViewContext
has changed.
This event is sent after layout is done and before paint is called. Note that you can still receive this event even if there was no prior call to layout.
Widgets must call this method after changing the clip region of their children.
Changes to the other parts of ViewContext
(cursor position and global origin) are tracked
internally.
source§impl EventCtx<'_, '_>
impl EventCtx<'_, '_>
sourcepub fn request_paint(&mut self)
pub fn request_paint(&mut self)
Request a paint
pass. This is equivalent to calling
request_paint_rect
for the widget’s paint_rect
.
Examples found in repository?
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
fn event(&mut self, ctx: &mut EventCtx, event: &Event, _data: &mut (), _env: &Env) {
match event {
Event::MouseDown(_) => {
self.t = 0.0;
ctx.request_anim_frame();
}
Event::AnimFrame(interval) => {
ctx.request_paint();
self.t += (*interval as f64) * 1e-9;
if self.t < 1.0 {
ctx.request_anim_frame();
} else {
// We might have t>1.0 at the end of the animation,
// we want to make sure the line points up at the
// end of the animation.
self.t = 0.0;
}
}
_ => (),
}
}
More examples
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 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 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419
fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut AppData, _env: &Env) {
match event {
Event::WindowConnected => {
ctx.request_paint();
let deadline = Duration::from_millis(data.iter_interval());
self.last_update = Instant::now();
self.timer_id = ctx.request_timer(deadline);
}
Event::Timer(id) => {
if *id == self.timer_id {
if !data.paused {
data.grid.evolve();
ctx.request_paint();
}
let deadline = Duration::from_millis(data.iter_interval());
self.last_update = Instant::now();
self.timer_id = ctx.request_timer(deadline);
}
}
Event::MouseDown(e) => {
if e.button == MouseButton::Left {
data.drawing = true;
let grid_pos_opt = self.grid_pos(e.pos);
grid_pos_opt
.iter()
.for_each(|pos| data.grid[*pos] = !data.grid[*pos]);
}
}
Event::MouseUp(e) => {
if e.button == MouseButton::Left {
data.drawing = false;
}
}
Event::MouseMove(e) => {
if data.drawing {
if let Some(grid_pos_opt) = self.grid_pos(e.pos) {
data.grid[grid_pos_opt] = true
}
}
}
_ => {}
}
}
fn lifecycle(
&mut self,
_ctx: &mut LifeCycleCtx,
_event: &LifeCycle,
_data: &AppData,
_env: &Env,
) {
}
fn update(&mut self, ctx: &mut UpdateCtx, old_data: &AppData, data: &AppData, _env: &Env) {
if (data.updates_per_second - old_data.updates_per_second).abs() > 0.001 {
let deadline = Duration::from_millis(data.iter_interval())
.checked_sub(Instant::now().duration_since(self.last_update))
.unwrap_or_else(|| Duration::from_secs(0));
self.timer_id = ctx.request_timer(deadline);
}
if data.grid != old_data.grid {
ctx.request_paint();
}
}
fn layout(
&mut self,
_layout_ctx: &mut LayoutCtx,
bc: &BoxConstraints,
_data: &AppData,
_env: &Env,
) -> Size {
let max_size = bc.max();
let min_side = max_size.height.min(max_size.width);
Size {
width: min_side,
height: min_side,
}
}
fn paint(&mut self, ctx: &mut PaintCtx, data: &AppData, _env: &Env) {
let size: Size = ctx.size();
let w0 = size.width / GRID_SIZE as f64;
let h0 = size.height / GRID_SIZE as f64;
let cell_size = Size {
width: w0,
height: h0,
};
self.cell_size = cell_size;
for row in 0..GRID_SIZE {
for col in 0..GRID_SIZE {
let pos = GridPos { row, col };
if data.grid[pos] {
let point = Point {
x: w0 * row as f64,
y: h0 * col as f64,
};
let rect = Rect::from_origin_size(point, cell_size);
// We divide by 2 so that the colour changes every 2 positions instead of every 1
ctx.fill(
rect,
&COLOURS[((pos.row * GRID_SIZE + pos.col) / 2) % COLOURS.len()],
);
}
}
}
}
}
// gives back positions of a glider pattern
// _____
// | *|
// |* *|
// | **|
// ‾‾‾‾‾
fn glider(left_most: GridPos) -> Option<[GridPos; 5]> {
let center = left_most.right()?;
Some([
left_most,
center.below()?.right()?,
center.below()?,
center.right()?,
center.above()?.right()?,
])
}
// gives back positions of a blinker pattern
// ___
// |*|
// |*|
// |*|
// ‾‾‾
fn blinker(top: GridPos) -> Option<[GridPos; 3]> {
let center = top.below()?;
Some([top, center, center.below()?])
}
fn make_widget() -> impl Widget<AppData> {
Flex::column()
.with_flex_child(
GameOfLifeWidget {
timer_id: TimerToken::INVALID,
cell_size: Size {
width: 0.0,
height: 0.0,
},
last_update: Instant::now(),
},
1.0,
)
.with_child(
Flex::column()
.with_child(
// a row with two buttons
Flex::row()
.with_flex_child(
// pause / resume button
Button::new(|data: &bool, _: &Env| match data {
true => "Resume",
false => "Pause",
})
.on_click(|ctx, data: &mut bool, _: &Env| {
*data = !*data;
ctx.request_layout();
})
.lens(AppData::paused)
.padding((5., 5.)),
1.0,
)
.with_flex_child(
// clear button
Button::new("Clear")
.on_click(|ctx, data: &mut Grid, _: &Env| {
data.clear();
ctx.request_paint();
})
.lens(AppData::grid)
.padding((5., 5.)),
1.0,
)
.padding(8.0),
)
.with_child(
Flex::row()
.with_child(
Label::new(|data: &AppData, _env: &_| {
format!("{:.2}updates/s", data.updates_per_second)
})
.padding(3.0),
)
.with_flex_child(
Slider::new()
.with_range(0.2, 20.0)
.expand_width()
.lens(AppData::updates_per_second),
1.,
)
.padding(8.0),
)
.background(BACKGROUND),
)
}
sourcepub fn request_paint_rect(&mut self, rect: Rect)
pub fn request_paint_rect(&mut self, rect: Rect)
Request a paint
pass for redrawing a rectangle, which is given
relative to our layout rectangle.
Examples found in repository?
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
fn event(&mut self, ctx: &mut EventCtx, ev: &Event, data: &mut Vector<Circle>, _env: &Env) {
if let Event::MouseDown(ev) = ev {
if ev.mods.shift() {
data.push_back(Circle {
pos: ev.pos,
time: Instant::now(),
});
} else if ev.mods.ctrl() {
data.retain(|c| {
if (c.pos - ev.pos).hypot() > RADIUS {
true
} else {
ctx.request_paint_rect(kurbo::Circle::new(c.pos, RADIUS).bounding_box());
false
}
});
} else {
// Move the circle to a new location, invalidating the old locations. The new location
// will be invalidated during AnimFrame.
for c in data.iter() {
ctx.request_paint_rect(kurbo::Circle::new(c.pos, RADIUS).bounding_box());
}
data.clear();
data.push_back(Circle {
pos: ev.pos,
time: Instant::now(),
});
}
ctx.request_anim_frame();
} else if let Event::AnimFrame(_) = ev {
for c in &*data {
ctx.request_paint_rect(kurbo::Circle::new(c.pos, RADIUS).bounding_box());
}
if !data.is_empty() {
ctx.request_anim_frame();
}
}
}
sourcepub fn request_layout(&mut self)
pub fn request_layout(&mut self)
Request a layout pass.
A Widget’s layout
method is always called when the widget tree
changes, or the window is resized.
If your widget would like to have layout called at any other time, (such as if it would like to change the layout of children in response to some event) it must call this method.
Examples found in repository?
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut u32, env: &Env) {
match event {
Event::WindowConnected => {
// Start the timer when the application launches
self.timer_id = ctx.request_timer(TIMER_INTERVAL);
}
Event::Timer(id) => {
if *id == self.timer_id {
self.adjust_box_pos(ctx.size());
ctx.request_layout();
self.timer_id = ctx.request_timer(TIMER_INTERVAL);
}
}
_ => (),
}
self.simple_box.event(ctx, event, data, env);
}
More examples
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 77 78 79 80 81 82 83
pub fn new() -> Self {
let info_label = Label::new(INFO_TEXT)
.with_line_break_mode(LineBreaking::WordWrap)
.padding(20.0)
.background(Color::rgba(0.2, 0.2, 0.2, 1.0));
let toggle_input_region = Button::new("Toggle Input Region")
.on_click(|ctx, data: &mut bool, _: &Env| {
*data = !*data;
tracing::debug!("Setting input region toggle to: {}", *data);
ctx.request_layout();
})
.lens(AppState::limit_input_region);
let toggle_titlebar = Button::new("Toggle TitleBar")
.on_click(|ctx, data: &mut bool, _: &Env| {
*data = !*data;
tracing::debug!("Setting titlebar visibility to: {}", *data);
ctx.window().show_titlebar(*data);
ctx.request_layout();
})
.lens(AppState::show_titlebar);
let toggle_always_on_top = Button::new("Toggle Always On Top")
.on_click(|ctx, data: &mut bool, _: &Env| {
*data = !*data;
tracing::debug!("Setting always on top to: {}", *data);
ctx.window().set_always_on_top(*data);
})
.lens(AppState::always_on_top);
let controls_flex = Flex::row()
.with_child(toggle_input_region)
.with_child(toggle_titlebar)
.with_child(toggle_always_on_top);
Self {
info_label: WidgetPod::new(info_label),
controls: WidgetPod::new(controls_flex),
}
}
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 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419
fn make_widget() -> impl Widget<AppData> {
Flex::column()
.with_flex_child(
GameOfLifeWidget {
timer_id: TimerToken::INVALID,
cell_size: Size {
width: 0.0,
height: 0.0,
},
last_update: Instant::now(),
},
1.0,
)
.with_child(
Flex::column()
.with_child(
// a row with two buttons
Flex::row()
.with_flex_child(
// pause / resume button
Button::new(|data: &bool, _: &Env| match data {
true => "Resume",
false => "Pause",
})
.on_click(|ctx, data: &mut bool, _: &Env| {
*data = !*data;
ctx.request_layout();
})
.lens(AppData::paused)
.padding((5., 5.)),
1.0,
)
.with_flex_child(
// clear button
Button::new("Clear")
.on_click(|ctx, data: &mut Grid, _: &Env| {
data.clear();
ctx.request_paint();
})
.lens(AppData::grid)
.padding((5., 5.)),
1.0,
)
.padding(8.0),
)
.with_child(
Flex::row()
.with_child(
Label::new(|data: &AppData, _env: &_| {
format!("{:.2}updates/s", data.updates_per_second)
})
.padding(3.0),
)
.with_flex_child(
Slider::new()
.with_range(0.2, 20.0)
.expand_width()
.lens(AppData::updates_per_second),
1.,
)
.padding(8.0),
)
.background(BACKGROUND),
)
}
sourcepub fn request_anim_frame(&mut self)
pub fn request_anim_frame(&mut self)
Request an AnimFrame
event.
Receiving AnimFrame
does not inherently mean a paint
invocation will follow.
If you want something actually painted you need to explicitly call request_paint
or request_paint_rect
when handling the AnimFrame
event.
Note that not requesting paint when handling the AnimFrame
event and then
recursively requesting another AnimFrame
can lead to rapid event fire,
which is probably not what you want and would most likely be wasted compute cycles.
Examples found in repository?
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
fn event(&mut self, ctx: &mut EventCtx, event: &Event, _data: &mut (), _env: &Env) {
match event {
Event::MouseDown(_) => {
self.t = 0.0;
ctx.request_anim_frame();
}
Event::AnimFrame(interval) => {
ctx.request_paint();
self.t += (*interval as f64) * 1e-9;
if self.t < 1.0 {
ctx.request_anim_frame();
} else {
// We might have t>1.0 at the end of the animation,
// we want to make sure the line points up at the
// end of the animation.
self.t = 0.0;
}
}
_ => (),
}
}
More examples
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
fn event(&mut self, ctx: &mut EventCtx, ev: &Event, data: &mut Vector<Circle>, _env: &Env) {
if let Event::MouseDown(ev) = ev {
if ev.mods.shift() {
data.push_back(Circle {
pos: ev.pos,
time: Instant::now(),
});
} else if ev.mods.ctrl() {
data.retain(|c| {
if (c.pos - ev.pos).hypot() > RADIUS {
true
} else {
ctx.request_paint_rect(kurbo::Circle::new(c.pos, RADIUS).bounding_box());
false
}
});
} else {
// Move the circle to a new location, invalidating the old locations. The new location
// will be invalidated during AnimFrame.
for c in data.iter() {
ctx.request_paint_rect(kurbo::Circle::new(c.pos, RADIUS).bounding_box());
}
data.clear();
data.push_back(Circle {
pos: ev.pos,
time: Instant::now(),
});
}
ctx.request_anim_frame();
} else if let Event::AnimFrame(_) = ev {
for c in &*data {
ctx.request_paint_rect(kurbo::Circle::new(c.pos, RADIUS).bounding_box());
}
if !data.is_empty() {
ctx.request_anim_frame();
}
}
}
sourcepub fn children_changed(&mut self)
pub fn children_changed(&mut self)
Indicate that your children have changed.
Widgets must call this method after adding a new child, removing a child or changing which
children are hidden (see should_propagate_to_hidden
).
sourcepub fn set_disabled(&mut self, disabled: bool)
pub fn set_disabled(&mut self, disabled: bool)
Set the disabled state for this widget.
Setting this to false
does not mean a widget is not still disabled; for instance it may
still be disabled by an ancestor. See is_disabled
for more information.
Calling this method during LifeCycle::DisabledChanged
has no effect.
sourcepub fn invalidate_text_input(&mut self, event: ImeInvalidation)
pub fn invalidate_text_input(&mut self, event: ImeInvalidation)
Indicate that text input state has changed.
A widget that accepts text input should call this anytime input state (such as the text or the selection) changes as a result of a non text-input event.
sourcepub fn new_sub_window<W: Widget<U> + 'static, U: Data>(
&mut self,
window_config: WindowConfig,
widget: W,
data: U,
env: Env
) -> WindowId
pub fn new_sub_window<W: Widget<U> + 'static, U: Data>( &mut self, window_config: WindowConfig, widget: W, data: U, env: Env ) -> WindowId
Create a new sub-window.
The sub-window will have its app data synchronised with caller’s nearest ancestor WidgetPod
.
‘U’ must be the type of the nearest surrounding WidgetPod
. The ‘data’ argument should be
the current value of data for that widget.
Examples found in repository?
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 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 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 event(&mut self, child: &mut W, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) {
let wait_duration = Duration::from_millis(500);
let resched_dur = Duration::from_millis(50);
let cursor_size = Size::new(15., 15.);
let now = Instant::now();
let new_state = match &self.state {
TooltipState::Fresh => match event {
Event::MouseMove(me) if ctx.is_hot() => Some(TooltipState::Waiting {
last_move: now,
timer_expire: now + wait_duration,
token: ctx.request_timer(wait_duration),
position_in_window_coordinates: me.window_pos,
}),
_ => None,
},
TooltipState::Waiting {
last_move,
timer_expire,
token,
position_in_window_coordinates,
} => match event {
Event::MouseMove(me) if ctx.is_hot() => {
let (cur_token, cur_expire) = if *timer_expire - now < resched_dur {
(ctx.request_timer(wait_duration), now + wait_duration)
} else {
(*token, *timer_expire)
};
Some(TooltipState::Waiting {
last_move: now,
timer_expire: cur_expire,
token: cur_token,
position_in_window_coordinates: me.window_pos,
})
}
Event::Timer(tok) if tok == token => {
let deadline = *last_move + wait_duration;
ctx.set_handled();
if deadline > now {
let wait_for = deadline - now;
tracing::info!("Waiting another {:?}", wait_for);
Some(TooltipState::Waiting {
last_move: *last_move,
timer_expire: deadline,
token: ctx.request_timer(wait_for),
position_in_window_coordinates: *position_in_window_coordinates,
})
} else {
let tooltip_position_in_window_coordinates =
(position_in_window_coordinates.to_vec2() + cursor_size.to_vec2())
.to_point();
let win_id = ctx.new_sub_window(
WindowConfig::default()
.show_titlebar(false)
.window_size_policy(WindowSizePolicy::Content)
.set_level(WindowLevel::Tooltip(ctx.window().clone()))
.set_position(tooltip_position_in_window_coordinates),
Label::<()>::new(self.tip.clone()),
(),
env.clone(),
);
Some(TooltipState::Showing(win_id))
}
}
_ => None,
},
TooltipState::Showing(win_id) => {
match event {
Event::MouseMove(me) if !ctx.is_hot() => {
// TODO another timer on leaving
tracing::info!("Sending close window for {:?}", win_id);
ctx.submit_command(CLOSE_WINDOW.to(*win_id));
Some(TooltipState::Waiting {
last_move: now,
timer_expire: now + wait_duration,
token: ctx.request_timer(wait_duration),
position_in_window_coordinates: me.window_pos,
})
}
_ => None,
}
}
};
if let Some(state) = new_state {
self.state = state;
}
if !ctx.is_handled() {
child.event(ctx, event, data, env);
}
}
fn lifecycle(
&mut self,
child: &mut W,
ctx: &mut LifeCycleCtx,
event: &LifeCycle,
data: &T,
env: &Env,
) {
if let LifeCycle::HotChanged(false) = event {
if let TooltipState::Showing(win_id) = self.state {
ctx.submit_command(CLOSE_WINDOW.to(win_id));
}
self.state = TooltipState::Fresh;
}
child.lifecycle(ctx, event, data, env)
}
}
struct DragWindowController {
init_pos: Option<Point>,
//dragging: bool
}
impl DragWindowController {
pub fn new() -> Self {
DragWindowController { init_pos: None }
}
}
impl<T, W: Widget<T>> Controller<T, W> for DragWindowController {
fn event(&mut self, child: &mut W, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) {
match event {
Event::MouseDown(me) if me.buttons.has_left() => {
ctx.set_active(true);
self.init_pos = Some(me.window_pos)
}
Event::MouseMove(me) if ctx.is_active() && me.buttons.has_left() => {
if let Some(init_pos) = self.init_pos {
let within_window_change = me.window_pos.to_vec2() - init_pos.to_vec2();
let old_pos = ctx.window().get_position();
let new_pos = old_pos + within_window_change;
tracing::info!(
"Drag {:?} ",
(
init_pos,
me.window_pos,
within_window_change,
old_pos,
new_pos
)
);
ctx.window().set_position(new_pos)
}
}
Event::MouseUp(_me) if ctx.is_active() => {
self.init_pos = None;
ctx.set_active(false)
}
_ => (),
}
child.event(ctx, event, data, env)
}
}
struct ScreenThing;
impl Widget<()> for ScreenThing {
fn event(&mut self, _ctx: &mut EventCtx, _event: &Event, _data: &mut (), _env: &Env) {}
fn lifecycle(&mut self, _ctx: &mut LifeCycleCtx, _event: &LifeCycle, _data: &(), _env: &Env) {}
fn update(&mut self, _ctx: &mut UpdateCtx, _old_data: &(), _data: &(), _env: &Env) {}
fn layout(
&mut self,
_ctx: &mut LayoutCtx,
bc: &BoxConstraints,
_data: &(),
_env: &Env,
) -> Size {
bc.constrain(Size::new(800.0, 600.0))
}
fn paint(&mut self, ctx: &mut PaintCtx, _data: &(), env: &Env) {
let sz = ctx.size();
let monitors = Screen::get_monitors();
let all = monitors
.iter()
.map(|x| x.virtual_rect())
.fold(Rect::ZERO, |s, r| r.union(s));
if all.width() > 0. && all.height() > 0. {
let trans = Affine::scale(f64::min(sz.width / all.width(), sz.height / all.height()))
* Affine::translate(all.origin().to_vec2()).inverse();
let font = env.get(theme::UI_FONT).family;
for (i, mon) in monitors.iter().enumerate() {
let vr = mon.virtual_rect();
let tr = trans.transform_rect_bbox(vr);
ctx.stroke(tr, &Color::WHITE, 1.0);
if let Ok(tl) = ctx
.text()
.new_text_layout(format!(
"{}:{}x{}@{},{}",
i,
vr.width(),
vr.height(),
vr.x0,
vr.y0
))
.max_width(tr.width() - 5.)
.font(font.clone(), env.get(theme::TEXT_SIZE_NORMAL))
.text_color(Color::WHITE)
.build()
{
ctx.draw_text(&tl, tr.center() - tl.size().to_vec2() * 0.5);
}
}
}
}
}
struct CancelClose;
impl<W: Widget<bool>> Controller<bool, W> for CancelClose {
fn event(
&mut self,
w: &mut W,
ctx: &mut EventCtx<'_, '_>,
event: &Event,
data: &mut bool,
env: &Env,
) {
match (&data, event) {
(false, Event::WindowCloseRequested) => ctx.set_handled(),
_ => w.event(ctx, event, data, env),
}
}
}
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)
}
sourcepub fn scroll_to_view(&mut self)
pub fn scroll_to_view(&mut self)
Scrolls this widget into view.
If this widget is only partially visible or not visible at all because of Scroll
s
it is wrapped in, they will do the minimum amount of scrolling necessary to bring this
widget fully into view.
If the widget is hidden
, this method has no effect.
This functionality is achieved by sending a SCROLL_TO_VIEW
notification.
source§impl EventCtx<'_, '_>
impl EventCtx<'_, '_>
sourcepub fn submit_command(&mut self, cmd: impl Into<Command>)
pub fn submit_command(&mut self, cmd: impl Into<Command>)
Submit a Command
to be run after this event is handled.
Commands are run in the order they are submitted; all commands
submitted during the handling of an event are executed before
the update
method is called; events submitted during update
are handled after painting.
Target::Auto
commands will be sent to the window containing the widget.
Examples found in repository?
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 77 78
fn ui_builder() -> impl Widget<State> {
let text = LocalizedString::new("hello-counter")
.with_arg("count", |data: &State, _env| data.menu_count.into());
let label = Label::new(text);
let inc_button =
Button::<State>::new("Add menu item").on_click(|_ctx, data, _env| data.menu_count += 1);
let dec_button = Button::<State>::new("Remove menu item")
.on_click(|_ctx, data, _env| data.menu_count = data.menu_count.saturating_sub(1));
let new_button = Button::<State>::new("New window").on_click(|ctx, _data, _env| {
ctx.submit_command(sys_cmds::NEW_FILE.to(Global));
});
let quit_button = Button::<State>::new("Quit app").on_click(|_ctx, _data, _env| {
Application::global().quit();
});
let mut col = Flex::column();
col.add_flex_child(Align::centered(Padding::new(5.0, label)), 1.0);
let mut row = Flex::row();
row.add_child(Padding::new(5.0, inc_button));
row.add_child(Padding::new(5.0, dec_button));
col.add_flex_child(Align::centered(row), 1.0);
let mut row = Flex::row();
row.add_child(Padding::new(5.0, new_button));
row.add_child(Padding::new(5.0, quit_button));
col.add_flex_child(Align::centered(row), 1.0);
let content = ControllerHost::new(col, ContextMenuController);
Glow::new(content)
}
More examples
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
fn ui_builder() -> impl Widget<String> {
let rs = FileSpec::new("Rust source", &["rs"]);
let txt = FileSpec::new("Text file", &["txt"]);
let other = FileSpec::new("Bogus file", &["foo", "bar", "baz"]);
// The options can also be generated at runtime,
// so to show that off we create a String for the default save name.
let default_save_name = String::from("MyFile.txt");
let save_dialog_options = FileDialogOptions::new()
.allowed_types(vec![rs, txt, other])
.default_type(txt)
.default_name(default_save_name)
.name_label("Target")
.title("Choose a target for this lovely file")
.button_text("Export");
let open_dialog_options = save_dialog_options
.clone()
.default_name("MySavedFile.txt")
.name_label("Source")
.title("Where did you put that file?")
.button_text("Import");
let input = TextBox::new();
let save = Button::new("Save").on_click(move |ctx, _, _| {
ctx.submit_command(druid::commands::SHOW_SAVE_PANEL.with(save_dialog_options.clone()))
});
let open = Button::new("Open").on_click(move |ctx, _, _| {
ctx.submit_command(druid::commands::SHOW_OPEN_PANEL.with(open_dialog_options.clone()))
});
let mut col = Flex::column();
col.add_child(input);
col.add_spacer(8.0);
col.add_child(save);
col.add_child(open);
Align::centered(col)
}
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
fn make_ui() -> impl Widget<OurData> {
// We can also generate these dynamically whenever we need it.
let id_two = WidgetId::next();
// We have a column with 2 labels and 2 buttons.
// Each of the 2 labels only has access to its own counter and is given a `WidgetId`.
// Both labels have a controller, this handles commands send to children.
// The 2 buttons send a command when clicked. Both send the exact same command.
// The key diference is that they both give a different `WidgetId` as target.
// This means that only the corresponding controller gets the command, and increments their counter.
Flex::column()
.with_child(
Label::dynamic(|data, _| format!("One: {data}"))
.controller(LabelControler)
.with_id(ID_ONE)
.lens(OurData::counter_one)
.padding(2.0),
)
.with_child(
Label::dynamic(|data, _| format!("Two: {data}"))
.controller(LabelControler)
.with_id(id_two)
.lens(OurData::counter_two)
.padding(2.0),
)
.with_child(
Button::<OurData>::new("Increment one")
.on_click(|ctx, _data, _env| ctx.submit_command(INCREMENT.to(ID_ONE)))
.padding(2.0),
)
.with_child(
Button::<OurData>::new("Increment two")
.on_click(move |ctx, _data, _env| ctx.submit_command(INCREMENT.to(id_two)))
.padding(2.0),
)
.padding(10.0)
}
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 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
fn event(&mut self, child: &mut W, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) {
let wait_duration = Duration::from_millis(500);
let resched_dur = Duration::from_millis(50);
let cursor_size = Size::new(15., 15.);
let now = Instant::now();
let new_state = match &self.state {
TooltipState::Fresh => match event {
Event::MouseMove(me) if ctx.is_hot() => Some(TooltipState::Waiting {
last_move: now,
timer_expire: now + wait_duration,
token: ctx.request_timer(wait_duration),
position_in_window_coordinates: me.window_pos,
}),
_ => None,
},
TooltipState::Waiting {
last_move,
timer_expire,
token,
position_in_window_coordinates,
} => match event {
Event::MouseMove(me) if ctx.is_hot() => {
let (cur_token, cur_expire) = if *timer_expire - now < resched_dur {
(ctx.request_timer(wait_duration), now + wait_duration)
} else {
(*token, *timer_expire)
};
Some(TooltipState::Waiting {
last_move: now,
timer_expire: cur_expire,
token: cur_token,
position_in_window_coordinates: me.window_pos,
})
}
Event::Timer(tok) if tok == token => {
let deadline = *last_move + wait_duration;
ctx.set_handled();
if deadline > now {
let wait_for = deadline - now;
tracing::info!("Waiting another {:?}", wait_for);
Some(TooltipState::Waiting {
last_move: *last_move,
timer_expire: deadline,
token: ctx.request_timer(wait_for),
position_in_window_coordinates: *position_in_window_coordinates,
})
} else {
let tooltip_position_in_window_coordinates =
(position_in_window_coordinates.to_vec2() + cursor_size.to_vec2())
.to_point();
let win_id = ctx.new_sub_window(
WindowConfig::default()
.show_titlebar(false)
.window_size_policy(WindowSizePolicy::Content)
.set_level(WindowLevel::Tooltip(ctx.window().clone()))
.set_position(tooltip_position_in_window_coordinates),
Label::<()>::new(self.tip.clone()),
(),
env.clone(),
);
Some(TooltipState::Showing(win_id))
}
}
_ => None,
},
TooltipState::Showing(win_id) => {
match event {
Event::MouseMove(me) if !ctx.is_hot() => {
// TODO another timer on leaving
tracing::info!("Sending close window for {:?}", win_id);
ctx.submit_command(CLOSE_WINDOW.to(*win_id));
Some(TooltipState::Waiting {
last_move: now,
timer_expire: now + wait_duration,
token: ctx.request_timer(wait_duration),
position_in_window_coordinates: me.window_pos,
})
}
_ => None,
}
}
};
if let Some(state) = new_state {
self.state = state;
}
if !ctx.is_handled() {
child.event(ctx, event, data, env);
}
}
sourcepub fn get_external_handle(&self) -> ExtEventSink
pub fn get_external_handle(&self) -> ExtEventSink
Returns an ExtEventSink
that can be moved between threads,
and can be used to submit commands back to the application.
Examples found in repository?
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
fn ui_builder() -> impl Widget<AppState> {
let button = Button::new("Start slow increment")
.on_click(|ctx, data: &mut AppState, _env| {
data.processing = true;
// In order to make sure that the other thread can communicate with the main thread we
// have to pass an external handle to the second thread.
// Using this handle we can send commands back to the main thread.
wrapped_slow_function(ctx.get_external_handle(), data.value);
})
.padding(5.0);
let button_placeholder = Flex::column()
.with_child(Label::new(LocalizedString::new("Processing...")).padding(5.0))
.with_child(Spinner::new());
// Hello-counter is defined in the built-in localisation file. This maps to "Current value is {count}"
// localised in english, french, or german. Every time the value is updated it shows the new value.
let text = LocalizedString::new("hello-counter")
.with_arg("count", |data: &AppState, _env| (data.value).into());
let label = Label::new(text).padding(5.0).center();
let either = Either::new(|data, _env| data.processing, button_placeholder, button);
Flex::column().with_child(label).with_child(either)
}
sourcepub fn request_timer(&mut self, deadline: Duration) -> TimerToken
pub fn request_timer(&mut self, deadline: Duration) -> TimerToken
Request a timer event.
The return value is a token, which can be used to associate the request with the event.
Examples found in repository?
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut u32, env: &Env) {
match event {
Event::WindowConnected => {
// Start the timer when the application launches
self.timer_id = ctx.request_timer(TIMER_INTERVAL);
}
Event::Timer(id) => {
if *id == self.timer_id {
self.adjust_box_pos(ctx.size());
ctx.request_layout();
self.timer_id = ctx.request_timer(TIMER_INTERVAL);
}
}
_ => (),
}
self.simple_box.event(ctx, event, data, env);
}
More examples
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut AppData, _env: &Env) {
match event {
Event::WindowConnected => {
ctx.request_paint();
let deadline = Duration::from_millis(data.iter_interval());
self.last_update = Instant::now();
self.timer_id = ctx.request_timer(deadline);
}
Event::Timer(id) => {
if *id == self.timer_id {
if !data.paused {
data.grid.evolve();
ctx.request_paint();
}
let deadline = Duration::from_millis(data.iter_interval());
self.last_update = Instant::now();
self.timer_id = ctx.request_timer(deadline);
}
}
Event::MouseDown(e) => {
if e.button == MouseButton::Left {
data.drawing = true;
let grid_pos_opt = self.grid_pos(e.pos);
grid_pos_opt
.iter()
.for_each(|pos| data.grid[*pos] = !data.grid[*pos]);
}
}
Event::MouseUp(e) => {
if e.button == MouseButton::Left {
data.drawing = false;
}
}
Event::MouseMove(e) => {
if data.drawing {
if let Some(grid_pos_opt) = self.grid_pos(e.pos) {
data.grid[grid_pos_opt] = true
}
}
}
_ => {}
}
}
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 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
fn event(&mut self, child: &mut W, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) {
let wait_duration = Duration::from_millis(500);
let resched_dur = Duration::from_millis(50);
let cursor_size = Size::new(15., 15.);
let now = Instant::now();
let new_state = match &self.state {
TooltipState::Fresh => match event {
Event::MouseMove(me) if ctx.is_hot() => Some(TooltipState::Waiting {
last_move: now,
timer_expire: now + wait_duration,
token: ctx.request_timer(wait_duration),
position_in_window_coordinates: me.window_pos,
}),
_ => None,
},
TooltipState::Waiting {
last_move,
timer_expire,
token,
position_in_window_coordinates,
} => match event {
Event::MouseMove(me) if ctx.is_hot() => {
let (cur_token, cur_expire) = if *timer_expire - now < resched_dur {
(ctx.request_timer(wait_duration), now + wait_duration)
} else {
(*token, *timer_expire)
};
Some(TooltipState::Waiting {
last_move: now,
timer_expire: cur_expire,
token: cur_token,
position_in_window_coordinates: me.window_pos,
})
}
Event::Timer(tok) if tok == token => {
let deadline = *last_move + wait_duration;
ctx.set_handled();
if deadline > now {
let wait_for = deadline - now;
tracing::info!("Waiting another {:?}", wait_for);
Some(TooltipState::Waiting {
last_move: *last_move,
timer_expire: deadline,
token: ctx.request_timer(wait_for),
position_in_window_coordinates: *position_in_window_coordinates,
})
} else {
let tooltip_position_in_window_coordinates =
(position_in_window_coordinates.to_vec2() + cursor_size.to_vec2())
.to_point();
let win_id = ctx.new_sub_window(
WindowConfig::default()
.show_titlebar(false)
.window_size_policy(WindowSizePolicy::Content)
.set_level(WindowLevel::Tooltip(ctx.window().clone()))
.set_position(tooltip_position_in_window_coordinates),
Label::<()>::new(self.tip.clone()),
(),
env.clone(),
);
Some(TooltipState::Showing(win_id))
}
}
_ => None,
},
TooltipState::Showing(win_id) => {
match event {
Event::MouseMove(me) if !ctx.is_hot() => {
// TODO another timer on leaving
tracing::info!("Sending close window for {:?}", win_id);
ctx.submit_command(CLOSE_WINDOW.to(*win_id));
Some(TooltipState::Waiting {
last_move: now,
timer_expire: now + wait_duration,
token: ctx.request_timer(wait_duration),
position_in_window_coordinates: me.window_pos,
})
}
_ => None,
}
}
};
if let Some(state) = new_state {
self.state = state;
}
if !ctx.is_handled() {
child.event(ctx, event, data, env);
}
}
source§impl EventCtx<'_, '_>
impl EventCtx<'_, '_>
sourcepub fn submit_notification(&mut self, note: impl Into<Command>)
pub fn submit_notification(&mut self, note: impl Into<Command>)
Submit a Notification
.
The provided argument can be a Selector
or a Command
; this lets
us work with the existing API for adding a payload to a Selector
.
If the argument is a Command
, the command’s target will be ignored.
Examples
const IMPORTANT_EVENT: Selector<String> = Selector::new("druid-example.important-event");
fn check_event(ctx: &mut EventCtx, event: &Event) {
if is_this_the_event_we_were_looking_for(event) {
ctx.submit_notification(IMPORTANT_EVENT.with("That's the one".to_string()))
}
}
sourcepub fn submit_notification_without_warning(&mut self, note: impl Into<Command>)
pub fn submit_notification_without_warning(&mut self, note: impl Into<Command>)
Submit a Notification
without warning.
In contrast to submit_notification
, calling this method will not result in an
“unhandled notification” warning.
sourcepub fn set_active(&mut self, active: bool)
pub fn set_active(&mut self, active: bool)
Set the “active” state of the widget.
See EventCtx::is_active
.
Examples found in repository?
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
fn event(&mut self, child: &mut W, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) {
match event {
Event::MouseDown(me) if me.buttons.has_left() => {
ctx.set_active(true);
self.init_pos = Some(me.window_pos)
}
Event::MouseMove(me) if ctx.is_active() && me.buttons.has_left() => {
if let Some(init_pos) = self.init_pos {
let within_window_change = me.window_pos.to_vec2() - init_pos.to_vec2();
let old_pos = ctx.window().get_position();
let new_pos = old_pos + within_window_change;
tracing::info!(
"Drag {:?} ",
(
init_pos,
me.window_pos,
within_window_change,
old_pos,
new_pos
)
);
ctx.window().set_position(new_pos)
}
}
Event::MouseUp(_me) if ctx.is_active() => {
self.init_pos = None;
ctx.set_active(false)
}
_ => (),
}
child.event(ctx, event, data, env)
}
sourcepub fn new_window<T: Any>(&mut self, desc: WindowDesc<T>)
pub fn new_window<T: Any>(&mut self, desc: WindowDesc<T>)
Create a new window.
T
must be the application’s root Data
type (the type provided to AppLauncher::launch
).
Show the context menu in the window containing the current widget.
T
must be the application’s root Data
type (the type provided to AppLauncher::launch
).
Examples found in repository?
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
fn event(
&mut self,
child: &mut W,
ctx: &mut EventCtx,
event: &Event,
data: &mut State,
env: &Env,
) {
match event {
Event::MouseDown(ref mouse) if mouse.button.is_right() => {
ctx.show_context_menu(make_context_menu(), mouse.pos);
}
_ => child.event(ctx, event, data, env),
}
}
sourcepub fn set_handled(&mut self)
pub fn set_handled(&mut self)
Set the event as “handled”, which stops its propagation to other widgets.
Examples found in repository?
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 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
fn event(&mut self, child: &mut W, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) {
let wait_duration = Duration::from_millis(500);
let resched_dur = Duration::from_millis(50);
let cursor_size = Size::new(15., 15.);
let now = Instant::now();
let new_state = match &self.state {
TooltipState::Fresh => match event {
Event::MouseMove(me) if ctx.is_hot() => Some(TooltipState::Waiting {
last_move: now,
timer_expire: now + wait_duration,
token: ctx.request_timer(wait_duration),
position_in_window_coordinates: me.window_pos,
}),
_ => None,
},
TooltipState::Waiting {
last_move,
timer_expire,
token,
position_in_window_coordinates,
} => match event {
Event::MouseMove(me) if ctx.is_hot() => {
let (cur_token, cur_expire) = if *timer_expire - now < resched_dur {
(ctx.request_timer(wait_duration), now + wait_duration)
} else {
(*token, *timer_expire)
};
Some(TooltipState::Waiting {
last_move: now,
timer_expire: cur_expire,
token: cur_token,
position_in_window_coordinates: me.window_pos,
})
}
Event::Timer(tok) if tok == token => {
let deadline = *last_move + wait_duration;
ctx.set_handled();
if deadline > now {
let wait_for = deadline - now;
tracing::info!("Waiting another {:?}", wait_for);
Some(TooltipState::Waiting {
last_move: *last_move,
timer_expire: deadline,
token: ctx.request_timer(wait_for),
position_in_window_coordinates: *position_in_window_coordinates,
})
} else {
let tooltip_position_in_window_coordinates =
(position_in_window_coordinates.to_vec2() + cursor_size.to_vec2())
.to_point();
let win_id = ctx.new_sub_window(
WindowConfig::default()
.show_titlebar(false)
.window_size_policy(WindowSizePolicy::Content)
.set_level(WindowLevel::Tooltip(ctx.window().clone()))
.set_position(tooltip_position_in_window_coordinates),
Label::<()>::new(self.tip.clone()),
(),
env.clone(),
);
Some(TooltipState::Showing(win_id))
}
}
_ => None,
},
TooltipState::Showing(win_id) => {
match event {
Event::MouseMove(me) if !ctx.is_hot() => {
// TODO another timer on leaving
tracing::info!("Sending close window for {:?}", win_id);
ctx.submit_command(CLOSE_WINDOW.to(*win_id));
Some(TooltipState::Waiting {
last_move: now,
timer_expire: now + wait_duration,
token: ctx.request_timer(wait_duration),
position_in_window_coordinates: me.window_pos,
})
}
_ => None,
}
}
};
if let Some(state) = new_state {
self.state = state;
}
if !ctx.is_handled() {
child.event(ctx, event, data, env);
}
}
fn lifecycle(
&mut self,
child: &mut W,
ctx: &mut LifeCycleCtx,
event: &LifeCycle,
data: &T,
env: &Env,
) {
if let LifeCycle::HotChanged(false) = event {
if let TooltipState::Showing(win_id) = self.state {
ctx.submit_command(CLOSE_WINDOW.to(win_id));
}
self.state = TooltipState::Fresh;
}
child.lifecycle(ctx, event, data, env)
}
}
struct DragWindowController {
init_pos: Option<Point>,
//dragging: bool
}
impl DragWindowController {
pub fn new() -> Self {
DragWindowController { init_pos: None }
}
}
impl<T, W: Widget<T>> Controller<T, W> for DragWindowController {
fn event(&mut self, child: &mut W, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) {
match event {
Event::MouseDown(me) if me.buttons.has_left() => {
ctx.set_active(true);
self.init_pos = Some(me.window_pos)
}
Event::MouseMove(me) if ctx.is_active() && me.buttons.has_left() => {
if let Some(init_pos) = self.init_pos {
let within_window_change = me.window_pos.to_vec2() - init_pos.to_vec2();
let old_pos = ctx.window().get_position();
let new_pos = old_pos + within_window_change;
tracing::info!(
"Drag {:?} ",
(
init_pos,
me.window_pos,
within_window_change,
old_pos,
new_pos
)
);
ctx.window().set_position(new_pos)
}
}
Event::MouseUp(_me) if ctx.is_active() => {
self.init_pos = None;
ctx.set_active(false)
}
_ => (),
}
child.event(ctx, event, data, env)
}
}
struct ScreenThing;
impl Widget<()> for ScreenThing {
fn event(&mut self, _ctx: &mut EventCtx, _event: &Event, _data: &mut (), _env: &Env) {}
fn lifecycle(&mut self, _ctx: &mut LifeCycleCtx, _event: &LifeCycle, _data: &(), _env: &Env) {}
fn update(&mut self, _ctx: &mut UpdateCtx, _old_data: &(), _data: &(), _env: &Env) {}
fn layout(
&mut self,
_ctx: &mut LayoutCtx,
bc: &BoxConstraints,
_data: &(),
_env: &Env,
) -> Size {
bc.constrain(Size::new(800.0, 600.0))
}
fn paint(&mut self, ctx: &mut PaintCtx, _data: &(), env: &Env) {
let sz = ctx.size();
let monitors = Screen::get_monitors();
let all = monitors
.iter()
.map(|x| x.virtual_rect())
.fold(Rect::ZERO, |s, r| r.union(s));
if all.width() > 0. && all.height() > 0. {
let trans = Affine::scale(f64::min(sz.width / all.width(), sz.height / all.height()))
* Affine::translate(all.origin().to_vec2()).inverse();
let font = env.get(theme::UI_FONT).family;
for (i, mon) in monitors.iter().enumerate() {
let vr = mon.virtual_rect();
let tr = trans.transform_rect_bbox(vr);
ctx.stroke(tr, &Color::WHITE, 1.0);
if let Ok(tl) = ctx
.text()
.new_text_layout(format!(
"{}:{}x{}@{},{}",
i,
vr.width(),
vr.height(),
vr.x0,
vr.y0
))
.max_width(tr.width() - 5.)
.font(font.clone(), env.get(theme::TEXT_SIZE_NORMAL))
.text_color(Color::WHITE)
.build()
{
ctx.draw_text(&tl, tr.center() - tl.size().to_vec2() * 0.5);
}
}
}
}
}
struct CancelClose;
impl<W: Widget<bool>> Controller<bool, W> for CancelClose {
fn event(
&mut self,
w: &mut W,
ctx: &mut EventCtx<'_, '_>,
event: &Event,
data: &mut bool,
env: &Env,
) {
match (&data, event) {
(false, Event::WindowCloseRequested) => ctx.set_handled(),
_ => w.event(ctx, event, data, env),
}
}
sourcepub fn is_handled(&self) -> bool
pub fn is_handled(&self) -> bool
Determine whether the event has been handled by some other widget.
Examples found in repository?
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 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
fn event(&mut self, child: &mut W, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) {
let wait_duration = Duration::from_millis(500);
let resched_dur = Duration::from_millis(50);
let cursor_size = Size::new(15., 15.);
let now = Instant::now();
let new_state = match &self.state {
TooltipState::Fresh => match event {
Event::MouseMove(me) if ctx.is_hot() => Some(TooltipState::Waiting {
last_move: now,
timer_expire: now + wait_duration,
token: ctx.request_timer(wait_duration),
position_in_window_coordinates: me.window_pos,
}),
_ => None,
},
TooltipState::Waiting {
last_move,
timer_expire,
token,
position_in_window_coordinates,
} => match event {
Event::MouseMove(me) if ctx.is_hot() => {
let (cur_token, cur_expire) = if *timer_expire - now < resched_dur {
(ctx.request_timer(wait_duration), now + wait_duration)
} else {
(*token, *timer_expire)
};
Some(TooltipState::Waiting {
last_move: now,
timer_expire: cur_expire,
token: cur_token,
position_in_window_coordinates: me.window_pos,
})
}
Event::Timer(tok) if tok == token => {
let deadline = *last_move + wait_duration;
ctx.set_handled();
if deadline > now {
let wait_for = deadline - now;
tracing::info!("Waiting another {:?}", wait_for);
Some(TooltipState::Waiting {
last_move: *last_move,
timer_expire: deadline,
token: ctx.request_timer(wait_for),
position_in_window_coordinates: *position_in_window_coordinates,
})
} else {
let tooltip_position_in_window_coordinates =
(position_in_window_coordinates.to_vec2() + cursor_size.to_vec2())
.to_point();
let win_id = ctx.new_sub_window(
WindowConfig::default()
.show_titlebar(false)
.window_size_policy(WindowSizePolicy::Content)
.set_level(WindowLevel::Tooltip(ctx.window().clone()))
.set_position(tooltip_position_in_window_coordinates),
Label::<()>::new(self.tip.clone()),
(),
env.clone(),
);
Some(TooltipState::Showing(win_id))
}
}
_ => None,
},
TooltipState::Showing(win_id) => {
match event {
Event::MouseMove(me) if !ctx.is_hot() => {
// TODO another timer on leaving
tracing::info!("Sending close window for {:?}", win_id);
ctx.submit_command(CLOSE_WINDOW.to(*win_id));
Some(TooltipState::Waiting {
last_move: now,
timer_expire: now + wait_duration,
token: ctx.request_timer(wait_duration),
position_in_window_coordinates: me.window_pos,
})
}
_ => None,
}
}
};
if let Some(state) = new_state {
self.state = state;
}
if !ctx.is_handled() {
child.event(ctx, event, data, env);
}
}
sourcepub fn request_focus(&mut self)
pub fn request_focus(&mut self)
Request keyboard focus.
Because only one widget can be focused at a time, multiple focus requests from different widgets during a single event cycle means that the last widget that requests focus will override the previous requests.
See is_focused
for more information about focus.
sourcepub fn set_focus(&mut self, target: WidgetId)
pub fn set_focus(&mut self, target: WidgetId)
Transfer focus to the widget with the given WidgetId
.
See is_focused
for more information about focus.
sourcepub fn focus_next(&mut self)
pub fn focus_next(&mut self)
Transfer focus to the next focusable widget.
This should only be called by a widget that currently has focus.
See is_focused
for more information about focus.
sourcepub fn focus_prev(&mut self)
pub fn focus_prev(&mut self)
Transfer focus to the previous focusable widget.
This should only be called by a widget that currently has focus.
See is_focused
for more information about focus.
sourcepub fn resign_focus(&mut self)
pub fn resign_focus(&mut self)
Give up focus.
This should only be called by a widget that currently has focus.
See is_focused
for more information about focus.
sourcepub fn request_update(&mut self)
pub fn request_update(&mut self)
Request an update cycle.
After this, update
will be called on the widget in the next update cycle, even
if there’s not a data change.
The use case for this method is when a container widget synthesizes data for its children. This is appropriate in specialized cases, but before reaching for this method, consider whether it might be better to refactor to be more idiomatic, in particular to make that data available in the app state.