Multiple mouse button support (#135)
Add support for primary, secondary and middle mouse buttons. Also improve ability to click things in low FPS situations. This introduces a lot of breaking changes: Backends/integrations now pass mouse events via the even stream. Response has an interface of mostly methods instead of public members. input.mouse is now input.pointer and has new interface. * Rename 'mouse' to 'pointer' everywhere (pointer = mouse or touch) * Make Response::clicked and Response::double_clicked into methods * Remove Response::active and add dragged() and interact_pointer_pos() * Support multiple mouse buttons * Make PointerState interface all methods * Make most members of Response private
This commit is contained in:
parent
18b9214575
commit
247026149c
47 changed files with 1052 additions and 580 deletions
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -11,7 +11,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
### Added ⭐
|
### Added ⭐
|
||||||
|
|
||||||
* `egui::popup::popup_below_widget`: show a popup area below another widget
|
* Add support for secondary and middle mouse buttons.
|
||||||
|
* `egui::popup::popup_below_widget`: show a popup area below another widget.
|
||||||
|
|
||||||
|
### Changed 🔧
|
||||||
|
|
||||||
|
* `mouse` has be renamed `pointer` everywhere (to make it clear it includes touches too).
|
||||||
|
* Most parts of `Response` are now methods, so `if ui.button("…").clicked {` is now `if ui.button("…").clicked() {`.
|
||||||
|
* `Response::active` is now gone. You can use `response.dragged()` or `response.clicked()` instead.
|
||||||
|
* Backend: pointer (mouse/touch) position and buttons are now passed to egui in the event stream.
|
||||||
|
|
||||||
|
### Fixed 🐛
|
||||||
|
|
||||||
|
* It is now possible to click widgets even when FPS is very low.
|
||||||
|
|
||||||
|
|
||||||
## 0.8.0 - 2021-01-17 - Grid layout & new visual style
|
## 0.8.0 - 2021-01-17 - Grid layout & new visual style
|
||||||
|
|
|
@ -51,7 +51,7 @@ ui.horizontal(|ui| {
|
||||||
ui.text_edit_singleline(&mut name);
|
ui.text_edit_singleline(&mut name);
|
||||||
});
|
});
|
||||||
ui.add(egui::Slider::u32(&mut age, 0..=120).text("age"));
|
ui.add(egui::Slider::u32(&mut age, 0..=120).text("age"));
|
||||||
if ui.button("Click each year").clicked {
|
if ui.button("Click each year").clicked() {
|
||||||
age += 1;
|
age += 1;
|
||||||
}
|
}
|
||||||
ui.label(format!("Hello '{}', age {}", name, age));
|
ui.label(format!("Hello '{}', age {}", name, age));
|
||||||
|
|
|
@ -29,7 +29,7 @@ impl epi::App for MyApp {
|
||||||
ui.text_edit_singleline(name);
|
ui.text_edit_singleline(name);
|
||||||
});
|
});
|
||||||
ui.add(egui::Slider::u32(age, 0..=120).text("age"));
|
ui.add(egui::Slider::u32(age, 0..=120).text("age"));
|
||||||
if ui.button("Click each year").clicked {
|
if ui.button("Click each year").clicked() {
|
||||||
*age += 1;
|
*age += 1;
|
||||||
}
|
}
|
||||||
ui.label(format!("Hello '{}', age {}", name, age));
|
ui.label(format!("Hello '{}', age {}", name, age));
|
||||||
|
|
|
@ -249,14 +249,14 @@ impl Prepared {
|
||||||
sense,
|
sense,
|
||||||
);
|
);
|
||||||
|
|
||||||
if move_response.active && movable {
|
if move_response.dragged() && movable {
|
||||||
state.pos += ctx.input().mouse.delta;
|
state.pos += ctx.input().pointer.delta();
|
||||||
}
|
}
|
||||||
|
|
||||||
state.pos = ctx.constrain_window_rect(state.rect()).min;
|
state.pos = ctx.constrain_window_rect(state.rect()).min;
|
||||||
|
|
||||||
if (move_response.active || move_response.clicked)
|
if (move_response.dragged() || move_response.clicked())
|
||||||
|| mouse_pressed_on_area(ctx, layer_id)
|
|| pointer_pressed_on_area(ctx, layer_id)
|
||||||
|| !ctx.memory().areas.visible_last_frame(&layer_id)
|
|| !ctx.memory().areas.visible_last_frame(&layer_id)
|
||||||
{
|
{
|
||||||
ctx.memory().areas.move_to_top(layer_id);
|
ctx.memory().areas.move_to_top(layer_id);
|
||||||
|
@ -268,9 +268,9 @@ impl Prepared {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_pressed_on_area(ctx: &Context, layer_id: LayerId) -> bool {
|
fn pointer_pressed_on_area(ctx: &Context, layer_id: LayerId) -> bool {
|
||||||
if let Some(mouse_pos) = ctx.input().mouse.pos {
|
if let Some(pointer_pos) = ctx.input().pointer.interact_pos() {
|
||||||
ctx.input().mouse.pressed && ctx.layer_id_at(mouse_pos) == Some(layer_id)
|
ctx.input().pointer.any_pressed() && ctx.layer_id_at(pointer_pos) == Some(layer_id)
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
|
@ -199,7 +199,7 @@ impl CollapsingHeader {
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut state = State::from_memory_with_default_open(ui.ctx(), id, default_open);
|
let mut state = State::from_memory_with_default_open(ui.ctx(), id, default_open);
|
||||||
if header_response.clicked {
|
if header_response.clicked() {
|
||||||
state.toggle(ui);
|
state.toggle(ui);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -84,7 +84,7 @@ pub fn combo_box(
|
||||||
.galley(text_rect.min, galley, text_style, visuals.text_color());
|
.galley(text_rect.min, galley, text_style, visuals.text_color());
|
||||||
});
|
});
|
||||||
|
|
||||||
if button_response.clicked {
|
if button_response.clicked() {
|
||||||
ui.memory().toggle_popup(popup_id);
|
ui.memory().toggle_popup(popup_id);
|
||||||
}
|
}
|
||||||
const MAX_COMBO_HEIGHT: f32 = 128.0;
|
const MAX_COMBO_HEIGHT: f32 = 128.0;
|
||||||
|
@ -118,7 +118,7 @@ fn button_frame(
|
||||||
outer_rect.set_height(outer_rect.height().at_least(interact_size.y));
|
outer_rect.set_height(outer_rect.height().at_least(interact_size.y));
|
||||||
|
|
||||||
let mut response = ui.interact(outer_rect, id, sense);
|
let mut response = ui.interact(outer_rect, id, sense);
|
||||||
response.active |= button_active;
|
response.is_pointer_button_down_on |= button_active;
|
||||||
let visuals = ui.style().interact(&response);
|
let visuals = ui.style().interact(&response);
|
||||||
|
|
||||||
ui.painter().set(
|
ui.painter().set(
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
/// Show a tooltip at the current mouse position (if any).
|
/// Show a tooltip at the current pointer position (if any).
|
||||||
///
|
///
|
||||||
/// Most of the time it is easier to use [`Response::on_hover_ui`].
|
/// Most of the time it is easier to use [`Response::on_hover_ui`].
|
||||||
///
|
///
|
||||||
|
@ -10,7 +10,7 @@ use crate::*;
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # let mut ui = egui::Ui::__test();
|
/// # let mut ui = egui::Ui::__test();
|
||||||
/// if ui.ui_contains_mouse() {
|
/// if ui.ui_contains_pointer() {
|
||||||
/// egui::show_tooltip(ui.ctx(), |ui| {
|
/// egui::show_tooltip(ui.ctx(), |ui| {
|
||||||
/// ui.label("Helpful text");
|
/// ui.label("Helpful text");
|
||||||
/// });
|
/// });
|
||||||
|
@ -21,9 +21,9 @@ pub fn show_tooltip(ctx: &CtxRef, add_contents: impl FnOnce(&mut Ui)) {
|
||||||
|
|
||||||
let window_pos = if let Some(tooltip_rect) = tooltip_rect {
|
let window_pos = if let Some(tooltip_rect) = tooltip_rect {
|
||||||
tooltip_rect.left_bottom()
|
tooltip_rect.left_bottom()
|
||||||
} else if let Some(mouse_pos) = ctx.input().mouse.pos {
|
} else if let Some(pointer_pos) = ctx.input().pointer.tooltip_pos() {
|
||||||
let expected_size = vec2(ctx.style().spacing.tooltip_width, 32.0);
|
let expected_size = vec2(ctx.style().spacing.tooltip_width, 32.0);
|
||||||
let position = mouse_pos + vec2(16.0, 16.0);
|
let position = pointer_pos + vec2(16.0, 16.0);
|
||||||
let position = position.min(ctx.input().screen_rect().right_bottom() - expected_size);
|
let position = position.min(ctx.input().screen_rect().right_bottom() - expected_size);
|
||||||
let position = position.max(ctx.input().screen_rect().left_top());
|
let position = position.max(ctx.input().screen_rect().left_top());
|
||||||
position
|
position
|
||||||
|
@ -41,7 +41,7 @@ pub fn show_tooltip(ctx: &CtxRef, add_contents: impl FnOnce(&mut Ui)) {
|
||||||
ctx.frame_state().tooltip_rect = Some(tooltip_rect.union(response.rect));
|
ctx.frame_state().tooltip_rect = Some(tooltip_rect.union(response.rect));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Show some text at the current mouse position (if any).
|
/// Show some text at the current pointer position (if any).
|
||||||
///
|
///
|
||||||
/// Most of the time it is easier to use [`Response::on_hover_text`].
|
/// Most of the time it is easier to use [`Response::on_hover_text`].
|
||||||
///
|
///
|
||||||
|
@ -49,7 +49,7 @@ pub fn show_tooltip(ctx: &CtxRef, add_contents: impl FnOnce(&mut Ui)) {
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # let mut ui = egui::Ui::__test();
|
/// # let mut ui = egui::Ui::__test();
|
||||||
/// if ui.ui_contains_mouse() {
|
/// if ui.ui_contains_pointer() {
|
||||||
/// egui::show_tooltip_text(ui.ctx(), "Helpful text");
|
/// egui::show_tooltip_text(ui.ctx(), "Helpful text");
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -89,7 +89,7 @@ fn show_tooltip_area(
|
||||||
/// # let ui = &mut egui::Ui::__test();
|
/// # let ui = &mut egui::Ui::__test();
|
||||||
/// let response = ui.button("Open popup");
|
/// let response = ui.button("Open popup");
|
||||||
/// let popup_id = ui.make_persistent_id("my_unique_id");
|
/// let popup_id = ui.make_persistent_id("my_unique_id");
|
||||||
/// if response.clicked {
|
/// if response.clicked() {
|
||||||
/// ui.memory().toggle_popup(popup_id);
|
/// ui.memory().toggle_popup(popup_id);
|
||||||
/// }
|
/// }
|
||||||
/// egui::popup::popup_below_widget(ui, popup_id, &response, |ui| {
|
/// egui::popup::popup_below_widget(ui, popup_id, &response, |ui| {
|
||||||
|
@ -121,7 +121,8 @@ pub fn popup_below_widget(
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
if ui.input().key_pressed(Key::Escape) || ui.input().mouse.click && !widget_response.clicked
|
if ui.input().key_pressed(Key::Escape)
|
||||||
|
|| ui.input().pointer.any_click() && !widget_response.clicked()
|
||||||
{
|
{
|
||||||
ui.memory().close_popup();
|
ui.memory().close_popup();
|
||||||
}
|
}
|
||||||
|
|
|
@ -191,12 +191,11 @@ impl Resize {
|
||||||
Rect::from_min_size(position + state.desired_size - corner_size, corner_size);
|
Rect::from_min_size(position + state.desired_size - corner_size, corner_size);
|
||||||
let corner_response = ui.interact(corner_rect, id.with("corner"), Sense::drag());
|
let corner_response = ui.interact(corner_rect, id.with("corner"), Sense::drag());
|
||||||
|
|
||||||
if corner_response.active {
|
if let Some(pointer_pos) = corner_response.interact_pointer_pos() {
|
||||||
if let Some(mouse_pos) = ui.input().mouse.pos {
|
user_requested_size =
|
||||||
user_requested_size =
|
Some(pointer_pos - position + 0.5 * corner_response.rect.size());
|
||||||
Some(mouse_pos - position + 0.5 * corner_response.rect.size());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(corner_response)
|
Some(corner_response)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -295,7 +294,7 @@ impl Resize {
|
||||||
if let Some(corner_response) = corner_response {
|
if let Some(corner_response) = corner_response {
|
||||||
paint_resize_corner(ui, &corner_response);
|
paint_resize_corner(ui, &corner_response);
|
||||||
|
|
||||||
if corner_response.hovered || corner_response.active {
|
if corner_response.hovered() || corner_response.dragged() {
|
||||||
ui.ctx().output().cursor_icon = CursorIcon::ResizeNwSe;
|
ui.ctx().output().cursor_icon = CursorIcon::ResizeNwSe;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -212,9 +212,9 @@ impl Prepared {
|
||||||
let content_response = ui.interact(inner_rect, id.with("area"), Sense::drag());
|
let content_response = ui.interact(inner_rect, id.with("area"), Sense::drag());
|
||||||
|
|
||||||
let input = ui.input();
|
let input = ui.input();
|
||||||
if content_response.active {
|
if content_response.dragged() {
|
||||||
state.offset.y -= input.mouse.delta.y;
|
state.offset.y -= input.pointer.delta().y;
|
||||||
state.vel = input.mouse.velocity;
|
state.vel = input.pointer.velocity();
|
||||||
} else {
|
} else {
|
||||||
let stop_speed = 20.0; // Pixels per second.
|
let stop_speed = 20.0; // Pixels per second.
|
||||||
let friction_coeff = 1000.0; // Pixels per second squared.
|
let friction_coeff = 1000.0; // Pixels per second squared.
|
||||||
|
@ -234,7 +234,7 @@ impl Prepared {
|
||||||
}
|
}
|
||||||
|
|
||||||
let max_offset = content_size.y - inner_rect.height();
|
let max_offset = content_size.y - inner_rect.height();
|
||||||
if ui.rect_contains_mouse(outer_rect) {
|
if ui.rect_contains_pointer(outer_rect) {
|
||||||
let mut frame_state = ui.ctx().frame_state();
|
let mut frame_state = ui.ctx().frame_state();
|
||||||
let scroll_delta = frame_state.scroll_delta;
|
let scroll_delta = frame_state.scroll_delta;
|
||||||
|
|
||||||
|
@ -283,26 +283,24 @@ impl Prepared {
|
||||||
let interact_id = id.with("vertical");
|
let interact_id = id.with("vertical");
|
||||||
let response = ui.interact(outer_scroll_rect, interact_id, Sense::click_and_drag());
|
let response = ui.interact(outer_scroll_rect, interact_id, Sense::click_and_drag());
|
||||||
|
|
||||||
if response.active {
|
if let Some(pointer_pos) = response.interact_pointer_pos() {
|
||||||
if let Some(mouse_pos) = ui.input().mouse.pos {
|
let scroll_start_offset_from_top =
|
||||||
let scroll_start_offset_from_top =
|
state.scroll_start_offset_from_top.get_or_insert_with(|| {
|
||||||
state.scroll_start_offset_from_top.get_or_insert_with(|| {
|
if handle_rect.contains(pointer_pos) {
|
||||||
if handle_rect.contains(mouse_pos) {
|
pointer_pos.y - handle_rect.top()
|
||||||
mouse_pos.y - handle_rect.top()
|
} else {
|
||||||
} else {
|
let handle_top_pos_at_bottom = bottom - handle_rect.height();
|
||||||
let handle_top_pos_at_bottom = bottom - handle_rect.height();
|
// Calculate the new handle top position, centering the handle on the mouse.
|
||||||
// Calculate the new handle top position, centering the handle on the mouse.
|
let new_handle_top_pos = clamp(
|
||||||
let new_handle_top_pos = clamp(
|
pointer_pos.y - handle_rect.height() / 2.0,
|
||||||
mouse_pos.y - handle_rect.height() / 2.0,
|
top..=handle_top_pos_at_bottom,
|
||||||
top..=handle_top_pos_at_bottom,
|
);
|
||||||
);
|
pointer_pos.y - new_handle_top_pos
|
||||||
mouse_pos.y - new_handle_top_pos
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
|
||||||
let new_handle_top = mouse_pos.y - *scroll_start_offset_from_top;
|
let new_handle_top = pointer_pos.y - *scroll_start_offset_from_top;
|
||||||
state.offset.y = remap(new_handle_top, top..=bottom, 0.0..=content_size.y);
|
state.offset.y = remap(new_handle_top, top..=bottom, 0.0..=content_size.y);
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
state.scroll_start_offset_from_top = None;
|
state.scroll_start_offset_from_top = None;
|
||||||
}
|
}
|
||||||
|
|
|
@ -363,12 +363,14 @@ impl<'open> Window<'open> {
|
||||||
ctx.style().visuals.widgets.active,
|
ctx.style().visuals.widgets.active,
|
||||||
);
|
);
|
||||||
} else if let Some(hover_interaction) = hover_interaction {
|
} else if let Some(hover_interaction) = hover_interaction {
|
||||||
paint_frame_interaction(
|
if ctx.input().pointer.has_pointer() {
|
||||||
&mut area_content_ui,
|
paint_frame_interaction(
|
||||||
outer_rect,
|
&mut area_content_ui,
|
||||||
hover_interaction,
|
outer_rect,
|
||||||
ctx.style().visuals.widgets.hovered,
|
hover_interaction,
|
||||||
);
|
ctx.style().visuals.widgets.hovered,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let full_response = area.end(ctx, area_content_ui);
|
let full_response = area.end(ctx, area_content_ui);
|
||||||
|
@ -448,24 +450,24 @@ fn interact(
|
||||||
|
|
||||||
fn move_and_resize_window(ctx: &Context, window_interaction: &WindowInteraction) -> Option<Rect> {
|
fn move_and_resize_window(ctx: &Context, window_interaction: &WindowInteraction) -> Option<Rect> {
|
||||||
window_interaction.set_cursor(ctx);
|
window_interaction.set_cursor(ctx);
|
||||||
let mouse_pos = ctx.input().mouse.pos?;
|
let pointer_pos = ctx.input().pointer.interact_pos()?;
|
||||||
let mut rect = window_interaction.start_rect; // prevent drift
|
let mut rect = window_interaction.start_rect; // prevent drift
|
||||||
|
|
||||||
if window_interaction.is_resize() {
|
if window_interaction.is_resize() {
|
||||||
if window_interaction.left {
|
if window_interaction.left {
|
||||||
rect.min.x = ctx.round_to_pixel(mouse_pos.x);
|
rect.min.x = ctx.round_to_pixel(pointer_pos.x);
|
||||||
} else if window_interaction.right {
|
} else if window_interaction.right {
|
||||||
rect.max.x = ctx.round_to_pixel(mouse_pos.x);
|
rect.max.x = ctx.round_to_pixel(pointer_pos.x);
|
||||||
}
|
}
|
||||||
|
|
||||||
if window_interaction.top {
|
if window_interaction.top {
|
||||||
rect.min.y = ctx.round_to_pixel(mouse_pos.y);
|
rect.min.y = ctx.round_to_pixel(pointer_pos.y);
|
||||||
} else if window_interaction.bottom {
|
} else if window_interaction.bottom {
|
||||||
rect.max.y = ctx.round_to_pixel(mouse_pos.y);
|
rect.max.y = ctx.round_to_pixel(pointer_pos.y);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// movement
|
// movement
|
||||||
rect = rect.translate(mouse_pos - ctx.input().mouse.press_origin?);
|
rect = rect.translate(pointer_pos - ctx.input().pointer.press_origin()?);
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(rect)
|
Some(rect)
|
||||||
|
@ -491,7 +493,7 @@ fn window_interaction(
|
||||||
if window_interaction.is_none() {
|
if window_interaction.is_none() {
|
||||||
if let Some(hover_window_interaction) = resize_hover(ctx, possible, area_layer_id, rect) {
|
if let Some(hover_window_interaction) = resize_hover(ctx, possible, area_layer_id, rect) {
|
||||||
hover_window_interaction.set_cursor(ctx);
|
hover_window_interaction.set_cursor(ctx);
|
||||||
if ctx.input().mouse.pressed {
|
if ctx.input().pointer.any_pressed() && ctx.input().pointer.any_down() {
|
||||||
ctx.memory().interaction.drag_id = Some(id);
|
ctx.memory().interaction.drag_id = Some(id);
|
||||||
ctx.memory().interaction.drag_is_window = true;
|
ctx.memory().interaction.drag_is_window = true;
|
||||||
window_interaction = Some(hover_window_interaction);
|
window_interaction = Some(hover_window_interaction);
|
||||||
|
@ -517,13 +519,13 @@ fn resize_hover(
|
||||||
area_layer_id: LayerId,
|
area_layer_id: LayerId,
|
||||||
rect: Rect,
|
rect: Rect,
|
||||||
) -> Option<WindowInteraction> {
|
) -> Option<WindowInteraction> {
|
||||||
let mouse_pos = ctx.input().mouse.pos?;
|
let pointer_pos = ctx.input().pointer.interact_pos()?;
|
||||||
|
|
||||||
if ctx.input().mouse.down && !ctx.input().mouse.pressed {
|
if ctx.input().pointer.any_down() && !ctx.input().pointer.any_pressed() {
|
||||||
return None; // already dragging (something)
|
return None; // already dragging (something)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(top_layer_id) = ctx.layer_id_at(mouse_pos) {
|
if let Some(top_layer_id) = ctx.layer_id_at(pointer_pos) {
|
||||||
if top_layer_id != area_layer_id && top_layer_id.order != Order::Background {
|
if top_layer_id != area_layer_id && top_layer_id.order != Order::Background {
|
||||||
return None; // Another window is on top here
|
return None; // Another window is on top here
|
||||||
}
|
}
|
||||||
|
@ -536,33 +538,33 @@ fn resize_hover(
|
||||||
|
|
||||||
let side_grab_radius = ctx.style().interaction.resize_grab_radius_side;
|
let side_grab_radius = ctx.style().interaction.resize_grab_radius_side;
|
||||||
let corner_grab_radius = ctx.style().interaction.resize_grab_radius_corner;
|
let corner_grab_radius = ctx.style().interaction.resize_grab_radius_corner;
|
||||||
if !rect.expand(side_grab_radius).contains(mouse_pos) {
|
if !rect.expand(side_grab_radius).contains(pointer_pos) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (mut left, mut right, mut top, mut bottom) = Default::default();
|
let (mut left, mut right, mut top, mut bottom) = Default::default();
|
||||||
if possible.resizable {
|
if possible.resizable {
|
||||||
right = (rect.right() - mouse_pos.x).abs() <= side_grab_radius;
|
right = (rect.right() - pointer_pos.x).abs() <= side_grab_radius;
|
||||||
bottom = (rect.bottom() - mouse_pos.y).abs() <= side_grab_radius;
|
bottom = (rect.bottom() - pointer_pos.y).abs() <= side_grab_radius;
|
||||||
|
|
||||||
if rect.right_bottom().distance(mouse_pos) < corner_grab_radius {
|
if rect.right_bottom().distance(pointer_pos) < corner_grab_radius {
|
||||||
right = true;
|
right = true;
|
||||||
bottom = true;
|
bottom = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if possible.movable {
|
if possible.movable {
|
||||||
left = (rect.left() - mouse_pos.x).abs() <= side_grab_radius;
|
left = (rect.left() - pointer_pos.x).abs() <= side_grab_radius;
|
||||||
top = (rect.top() - mouse_pos.y).abs() <= side_grab_radius;
|
top = (rect.top() - pointer_pos.y).abs() <= side_grab_radius;
|
||||||
|
|
||||||
if rect.right_top().distance(mouse_pos) < corner_grab_radius {
|
if rect.right_top().distance(pointer_pos) < corner_grab_radius {
|
||||||
right = true;
|
right = true;
|
||||||
top = true;
|
top = true;
|
||||||
}
|
}
|
||||||
if rect.left_top().distance(mouse_pos) < corner_grab_radius {
|
if rect.left_top().distance(pointer_pos) < corner_grab_radius {
|
||||||
left = true;
|
left = true;
|
||||||
top = true;
|
top = true;
|
||||||
}
|
}
|
||||||
if rect.left_bottom().distance(mouse_pos) < corner_grab_radius {
|
if rect.left_bottom().distance(pointer_pos) < corner_grab_radius {
|
||||||
left = true;
|
left = true;
|
||||||
bottom = true;
|
bottom = true;
|
||||||
}
|
}
|
||||||
|
@ -671,7 +673,7 @@ fn show_title_bar(
|
||||||
|
|
||||||
let (_id, rect) = ui.allocate_space(button_size);
|
let (_id, rect) = ui.allocate_space(button_size);
|
||||||
let collapse_button_response = ui.interact(rect, collapsing_id, Sense::click());
|
let collapse_button_response = ui.interact(rect, collapsing_id, Sense::click());
|
||||||
if collapse_button_response.clicked {
|
if collapse_button_response.clicked() {
|
||||||
collapsing.toggle(ui);
|
collapsing.toggle(ui);
|
||||||
}
|
}
|
||||||
let openness = collapsing.openness(ui.ctx(), collapsing_id);
|
let openness = collapsing.openness(ui.ctx(), collapsing_id);
|
||||||
|
@ -721,7 +723,7 @@ impl TitleBar {
|
||||||
|
|
||||||
if let Some(open) = open {
|
if let Some(open) = open {
|
||||||
// Add close button now that we know our full width:
|
// Add close button now that we know our full width:
|
||||||
if self.close_button_ui(ui).clicked {
|
if self.close_button_ui(ui).clicked() {
|
||||||
*open = false;
|
*open = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -752,7 +754,7 @@ impl TitleBar {
|
||||||
|
|
||||||
if ui
|
if ui
|
||||||
.interact(self.rect, self.id, Sense::click())
|
.interact(self.rect, self.id, Sense::click())
|
||||||
.double_clicked
|
.double_clicked()
|
||||||
&& collapsible
|
&& collapsible
|
||||||
{
|
{
|
||||||
collapsing.toggle(ui);
|
collapsing.toggle(ui);
|
||||||
|
|
|
@ -8,6 +8,7 @@ use std::sync::{
|
||||||
use crate::{
|
use crate::{
|
||||||
animation_manager::AnimationManager,
|
animation_manager::AnimationManager,
|
||||||
data::output::Output,
|
data::output::Output,
|
||||||
|
input_state::*,
|
||||||
layers::GraphicLayers,
|
layers::GraphicLayers,
|
||||||
mutex::{Mutex, MutexGuard},
|
mutex::{Mutex, MutexGuard},
|
||||||
paint::{stats::*, text::Fonts, *},
|
paint::{stats::*, text::Fonts, *},
|
||||||
|
@ -190,8 +191,8 @@ impl CtxRef {
|
||||||
let show_error = |pos: Pos2, text: String| {
|
let show_error = |pos: Pos2, text: String| {
|
||||||
let painter = self.debug_painter();
|
let painter = self.debug_painter();
|
||||||
let rect = painter.error(pos, text);
|
let rect = painter.error(pos, text);
|
||||||
if let Some(mouse_pos) = self.input.mouse.pos {
|
if let Some(pointer_pos) = self.input.pointer.tooltip_pos() {
|
||||||
if rect.contains(mouse_pos) {
|
if rect.contains(pointer_pos) {
|
||||||
painter.error(
|
painter.error(
|
||||||
rect.left_bottom() + vec2(2.0, 4.0),
|
rect.left_bottom() + vec2(2.0, 4.0),
|
||||||
"ID clashes happens when things like Windows or CollpasingHeaders share names,\n\
|
"ID clashes happens when things like Windows or CollpasingHeaders share names,\n\
|
||||||
|
@ -227,7 +228,7 @@ impl CtxRef {
|
||||||
sense: Sense,
|
sense: Sense,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
let interact_rect = rect.expand2((0.5 * item_spacing).min(Vec2::splat(5.0))); // make it easier to click
|
let interact_rect = rect.expand2((0.5 * item_spacing).min(Vec2::splat(5.0))); // make it easier to click
|
||||||
let hovered = self.rect_contains_mouse(layer_id, clip_rect.intersect(interact_rect));
|
let hovered = self.rect_contains_pointer(layer_id, clip_rect.intersect(interact_rect));
|
||||||
self.interact_with_hovered(layer_id, id, rect, sense, hovered)
|
self.interact_with_hovered(layer_id, id, rect, sense, hovered)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,26 +242,28 @@ impl CtxRef {
|
||||||
hovered: bool,
|
hovered: bool,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
let has_kb_focus = self.memory().has_kb_focus(id);
|
let has_kb_focus = self.memory().has_kb_focus(id);
|
||||||
|
|
||||||
// If the the focus is lost after the call to interact,
|
|
||||||
// this will be `false`, so `TextEdit` also sets this manually.
|
|
||||||
let lost_kb_focus = self.memory().lost_kb_focus(id);
|
let lost_kb_focus = self.memory().lost_kb_focus(id);
|
||||||
|
|
||||||
|
let mut response = Response {
|
||||||
|
ctx: self.clone(),
|
||||||
|
layer_id,
|
||||||
|
id,
|
||||||
|
rect,
|
||||||
|
sense,
|
||||||
|
hovered,
|
||||||
|
clicked: Default::default(),
|
||||||
|
double_clicked: Default::default(),
|
||||||
|
dragged: false,
|
||||||
|
drag_released: false,
|
||||||
|
is_pointer_button_down_on: false,
|
||||||
|
interact_pointer_pos: None,
|
||||||
|
has_kb_focus,
|
||||||
|
lost_kb_focus,
|
||||||
|
};
|
||||||
|
|
||||||
if sense == Sense::hover() || !layer_id.allow_interaction() {
|
if sense == Sense::hover() || !layer_id.allow_interaction() {
|
||||||
// Not interested or allowed input:
|
// Not interested or allowed input:
|
||||||
return Response {
|
return response;
|
||||||
ctx: self.clone(),
|
|
||||||
layer_id,
|
|
||||||
id,
|
|
||||||
rect,
|
|
||||||
sense,
|
|
||||||
hovered,
|
|
||||||
clicked: false,
|
|
||||||
double_clicked: false,
|
|
||||||
active: false,
|
|
||||||
has_kb_focus,
|
|
||||||
lost_kb_focus,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.register_interaction_id(id, rect.min);
|
self.register_interaction_id(id, rect.min);
|
||||||
|
@ -270,102 +273,62 @@ impl CtxRef {
|
||||||
memory.interaction.click_interest |= hovered && sense.click;
|
memory.interaction.click_interest |= hovered && sense.click;
|
||||||
memory.interaction.drag_interest |= hovered && sense.drag;
|
memory.interaction.drag_interest |= hovered && sense.drag;
|
||||||
|
|
||||||
let active =
|
response.dragged = memory.interaction.drag_id == Some(id);
|
||||||
memory.interaction.click_id == Some(id) || memory.interaction.drag_id == Some(id);
|
response.is_pointer_button_down_on =
|
||||||
|
memory.interaction.click_id == Some(id) || response.dragged;
|
||||||
|
|
||||||
if self.input.mouse.pressed {
|
for pointer_event in &self.input.pointer.pointer_events {
|
||||||
if hovered {
|
match pointer_event {
|
||||||
let mut response = Response {
|
PointerEvent::Moved(pos) => {
|
||||||
ctx: self.clone(),
|
if response.is_pointer_button_down_on {
|
||||||
layer_id,
|
response.interact_pointer_pos = Some(*pos);
|
||||||
id,
|
}
|
||||||
rect,
|
|
||||||
sense,
|
|
||||||
hovered: true,
|
|
||||||
clicked: false,
|
|
||||||
double_clicked: false,
|
|
||||||
active: false,
|
|
||||||
has_kb_focus,
|
|
||||||
lost_kb_focus,
|
|
||||||
};
|
|
||||||
|
|
||||||
if sense.click && memory.interaction.click_id.is_none() {
|
|
||||||
// start of a click
|
|
||||||
memory.interaction.click_id = Some(id);
|
|
||||||
response.active = true;
|
|
||||||
}
|
}
|
||||||
|
PointerEvent::Pressed(pos) => {
|
||||||
|
if hovered {
|
||||||
|
if sense.click && memory.interaction.click_id.is_none() {
|
||||||
|
// potential start of a click
|
||||||
|
memory.interaction.click_id = Some(id);
|
||||||
|
response.interact_pointer_pos = Some(*pos);
|
||||||
|
response.is_pointer_button_down_on = true;
|
||||||
|
}
|
||||||
|
|
||||||
if sense.drag
|
if sense.drag
|
||||||
&& (memory.interaction.drag_id.is_none() || memory.interaction.drag_is_window)
|
&& (memory.interaction.drag_id.is_none()
|
||||||
{
|
|| memory.interaction.drag_is_window)
|
||||||
// start of a drag
|
{
|
||||||
memory.interaction.drag_id = Some(id);
|
// potential start of a drag
|
||||||
memory.interaction.drag_is_window = false;
|
response.interact_pointer_pos = Some(*pos);
|
||||||
memory.window_interaction = None; // HACK: stop moving windows (if any)
|
memory.interaction.drag_id = Some(id);
|
||||||
response.active = true;
|
memory.interaction.drag_is_window = false;
|
||||||
|
memory.window_interaction = None; // HACK: stop moving windows (if any)
|
||||||
|
response.is_pointer_button_down_on = true;
|
||||||
|
response.dragged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
PointerEvent::Released(click) => {
|
||||||
|
response.drag_released = response.dragged;
|
||||||
|
response.dragged = false;
|
||||||
|
|
||||||
response
|
if hovered && response.is_pointer_button_down_on {
|
||||||
} else {
|
if let Some(click) = click {
|
||||||
// miss
|
let clicked = hovered && response.is_pointer_button_down_on;
|
||||||
Response {
|
response.interact_pointer_pos = Some(click.pos);
|
||||||
ctx: self.clone(),
|
response.clicked[click.button as usize] = clicked;
|
||||||
layer_id,
|
response.double_clicked[click.button as usize] =
|
||||||
id,
|
clicked && click.is_double();
|
||||||
rect,
|
}
|
||||||
sense,
|
}
|
||||||
hovered,
|
|
||||||
clicked: false,
|
|
||||||
double_clicked: false,
|
|
||||||
active: false,
|
|
||||||
has_kb_focus,
|
|
||||||
lost_kb_focus,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if self.input.mouse.released {
|
|
||||||
let clicked = hovered && active && self.input.mouse.could_be_click;
|
|
||||||
Response {
|
|
||||||
ctx: self.clone(),
|
|
||||||
layer_id,
|
|
||||||
id,
|
|
||||||
rect,
|
|
||||||
sense,
|
|
||||||
hovered,
|
|
||||||
clicked,
|
|
||||||
double_clicked: clicked && self.input.mouse.double_click,
|
|
||||||
active,
|
|
||||||
has_kb_focus,
|
|
||||||
lost_kb_focus,
|
|
||||||
}
|
|
||||||
} else if self.input.mouse.down {
|
|
||||||
Response {
|
|
||||||
ctx: self.clone(),
|
|
||||||
layer_id,
|
|
||||||
id,
|
|
||||||
rect,
|
|
||||||
sense,
|
|
||||||
hovered: hovered && active,
|
|
||||||
clicked: false,
|
|
||||||
double_clicked: false,
|
|
||||||
active,
|
|
||||||
has_kb_focus,
|
|
||||||
lost_kb_focus,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Response {
|
|
||||||
ctx: self.clone(),
|
|
||||||
layer_id,
|
|
||||||
id,
|
|
||||||
rect,
|
|
||||||
sense,
|
|
||||||
hovered,
|
|
||||||
clicked: false,
|
|
||||||
double_clicked: false,
|
|
||||||
active,
|
|
||||||
has_kb_focus,
|
|
||||||
lost_kb_focus,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.input.pointer.any_down() {
|
||||||
|
response.hovered &= response.is_pointer_button_down_on; // we don't hover widgets while interacting with *other* widgets
|
||||||
|
}
|
||||||
|
|
||||||
|
response
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn debug_painter(&self) -> Painter {
|
pub fn debug_painter(&self) -> Painter {
|
||||||
|
@ -648,12 +611,12 @@ impl Context {
|
||||||
|
|
||||||
// ---------------------------------------------------------------------
|
// ---------------------------------------------------------------------
|
||||||
|
|
||||||
/// Is the mouse over any egui area?
|
/// Is the pointer (mouse/touch) over any egui area?
|
||||||
pub fn is_mouse_over_area(&self) -> bool {
|
pub fn is_pointer_over_area(&self) -> bool {
|
||||||
if let Some(mouse_pos) = self.input.mouse.pos {
|
if let Some(pointer_pos) = self.input.pointer.interact_pos() {
|
||||||
if let Some(layer) = self.layer_id_at(mouse_pos) {
|
if let Some(layer) = self.layer_id_at(pointer_pos) {
|
||||||
if layer.order == Order::Background {
|
if layer.order == Order::Background {
|
||||||
!self.frame_state().unused_rect.contains(mouse_pos)
|
!self.frame_state().unused_rect.contains(pointer_pos)
|
||||||
} else {
|
} else {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -665,19 +628,29 @@ impl Context {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// True if egui is currently interested in the mouse.
|
/// True if egui is currently interested in the pointer (mouse or touch).
|
||||||
/// Could be the mouse is hovering over a [`Window`] or the user is dragging a widget.
|
/// Could be the pointer is hovering over a [`Window`] or the user is dragging a widget.
|
||||||
/// If `false`, the mouse is outside of any egui area and so
|
/// If `false`, the pointer is outside of any egui area and so
|
||||||
/// you may be interested in what it is doing (e.g. controlling your game).
|
/// you may be interested in what it is doing (e.g. controlling your game).
|
||||||
/// Returns `false` if a drag started outside of egui and then moved over an egui area.
|
/// Returns `false` if a drag started outside of egui and then moved over an egui area.
|
||||||
pub fn wants_mouse_input(&self) -> bool {
|
pub fn wants_pointer_input(&self) -> bool {
|
||||||
self.is_using_mouse() || (self.is_mouse_over_area() && !self.input().mouse.down)
|
self.is_using_pointer() || (self.is_pointer_over_area() && !self.input().pointer.any_down())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Is egui currently using the mouse position (e.g. dragging a slider).
|
/// Is egui currently using the pointer position (e.g. dragging a slider).
|
||||||
/// NOTE: this will return `false` if the mouse is just hovering over an egui area.
|
/// NOTE: this will return `false` if the pointer is just hovering over an egui area.
|
||||||
|
pub fn is_using_pointer(&self) -> bool {
|
||||||
|
self.memory().interaction.is_using_pointer()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deprecated = "Renamed wants_pointer_input"]
|
||||||
|
pub fn wants_mouse_input(&self) -> bool {
|
||||||
|
self.wants_pointer_input()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deprecated = "Renamed is_using_pointer"]
|
||||||
pub fn is_using_mouse(&self) -> bool {
|
pub fn is_using_mouse(&self) -> bool {
|
||||||
self.memory().interaction.is_using_mouse()
|
self.is_using_pointer()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If `true`, egui is currently listening on text input (e.g. typing text in a [`TextEdit`]).
|
/// If `true`, egui is currently listening on text input (e.g. typing text in a [`TextEdit`]).
|
||||||
|
@ -698,9 +671,9 @@ impl Context {
|
||||||
self.memory().layer_id_at(pos, resize_grab_radius_side)
|
self.memory().layer_id_at(pos, resize_grab_radius_side)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn rect_contains_mouse(&self, layer_id: LayerId, rect: Rect) -> bool {
|
pub(crate) fn rect_contains_pointer(&self, layer_id: LayerId, rect: Rect) -> bool {
|
||||||
if let Some(mouse_pos) = self.input.mouse.pos {
|
if let Some(pointer_pos) = self.input.pointer.interact_pos() {
|
||||||
rect.contains(mouse_pos) && self.layer_id_at(mouse_pos) == Some(layer_id)
|
rect.contains(pointer_pos) && self.layer_id_at(pointer_pos) == Some(layer_id)
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
@ -766,10 +739,12 @@ impl Context {
|
||||||
pub fn inspection_ui(&self, ui: &mut Ui) {
|
pub fn inspection_ui(&self, ui: &mut Ui) {
|
||||||
use crate::containers::*;
|
use crate::containers::*;
|
||||||
|
|
||||||
ui.label(format!("Is using mouse: {}", self.is_using_mouse()))
|
ui.label(format!("Is using pointer: {}", self.is_using_pointer()))
|
||||||
.on_hover_text("Is egui currently using the mouse actively (e.g. dragging a slider)?");
|
.on_hover_text(
|
||||||
ui.label(format!("Wants mouse input: {}", self.wants_mouse_input()))
|
"Is egui currently using the pointer actively (e.g. dragging a slider)?",
|
||||||
.on_hover_text("Is egui currently interested in the location of the mouse (either because it is in use, or because it is hovering over a window).");
|
);
|
||||||
|
ui.label(format!("Wants pointer input: {}", self.wants_pointer_input()))
|
||||||
|
.on_hover_text("Is egui currently interested in the location of the pointer (either because it is in use, or because it is hovering over a window).");
|
||||||
ui.label(format!(
|
ui.label(format!(
|
||||||
"Wants keyboard input: {}",
|
"Wants keyboard input: {}",
|
||||||
self.wants_keyboard_input()
|
self.wants_keyboard_input()
|
||||||
|
@ -792,7 +767,7 @@ impl Context {
|
||||||
if ui
|
if ui
|
||||||
.button("Reset all")
|
.button("Reset all")
|
||||||
.on_hover_text("Reset all egui state")
|
.on_hover_text("Reset all egui state")
|
||||||
.clicked
|
.clicked()
|
||||||
{
|
{
|
||||||
*self.memory() = Default::default();
|
*self.memory() = Default::default();
|
||||||
}
|
}
|
||||||
|
@ -802,7 +777,7 @@ impl Context {
|
||||||
"{} areas (window positions)",
|
"{} areas (window positions)",
|
||||||
self.memory().areas.count()
|
self.memory().areas.count()
|
||||||
));
|
));
|
||||||
if ui.button("Reset").clicked {
|
if ui.button("Reset").clicked() {
|
||||||
self.memory().areas = Default::default();
|
self.memory().areas = Default::default();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -835,28 +810,28 @@ impl Context {
|
||||||
"{} collapsing headers",
|
"{} collapsing headers",
|
||||||
self.memory().collapsing_headers.len()
|
self.memory().collapsing_headers.len()
|
||||||
));
|
));
|
||||||
if ui.button("Reset").clicked {
|
if ui.button("Reset").clicked() {
|
||||||
self.memory().collapsing_headers = Default::default();
|
self.memory().collapsing_headers = Default::default();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label(format!("{} menu bars", self.memory().menu_bar.len()));
|
ui.label(format!("{} menu bars", self.memory().menu_bar.len()));
|
||||||
if ui.button("Reset").clicked {
|
if ui.button("Reset").clicked() {
|
||||||
self.memory().menu_bar = Default::default();
|
self.memory().menu_bar = Default::default();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label(format!("{} scroll areas", self.memory().scroll_areas.len()));
|
ui.label(format!("{} scroll areas", self.memory().scroll_areas.len()));
|
||||||
if ui.button("Reset").clicked {
|
if ui.button("Reset").clicked() {
|
||||||
self.memory().scroll_areas = Default::default();
|
self.memory().scroll_areas = Default::default();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label(format!("{} resize areas", self.memory().resize.len()));
|
ui.label(format!("{} resize areas", self.memory().resize.len()));
|
||||||
if ui.button("Reset").clicked {
|
if ui.button("Reset").clicked() {
|
||||||
self.memory().resize = Default::default();
|
self.memory().resize = Default::default();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,13 +9,6 @@ use crate::math::*;
|
||||||
/// All coordinates are in points (logical pixels) with origin (0, 0) in the top left corner.
|
/// All coordinates are in points (logical pixels) with origin (0, 0) in the top left corner.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct RawInput {
|
pub struct RawInput {
|
||||||
/// Is the button currently down?
|
|
||||||
/// NOTE: egui currently only supports the primary mouse button.
|
|
||||||
pub mouse_down: bool,
|
|
||||||
|
|
||||||
/// Current position of the mouse in points.
|
|
||||||
pub mouse_pos: Option<Pos2>,
|
|
||||||
|
|
||||||
/// How many points (logical pixels) the user scrolled
|
/// How many points (logical pixels) the user scrolled
|
||||||
pub scroll_delta: Vec2,
|
pub scroll_delta: Vec2,
|
||||||
|
|
||||||
|
@ -56,8 +49,6 @@ impl Default for RawInput {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
#![allow(deprecated)] // for screen_size
|
#![allow(deprecated)] // for screen_size
|
||||||
Self {
|
Self {
|
||||||
mouse_down: false,
|
|
||||||
mouse_pos: None,
|
|
||||||
scroll_delta: Vec2::zero(),
|
scroll_delta: Vec2::zero(),
|
||||||
screen_size: Default::default(),
|
screen_size: Default::default(),
|
||||||
screen_rect: None,
|
screen_rect: None,
|
||||||
|
@ -75,8 +66,6 @@ impl RawInput {
|
||||||
pub fn take(&mut self) -> RawInput {
|
pub fn take(&mut self) -> RawInput {
|
||||||
#![allow(deprecated)] // for screen_size
|
#![allow(deprecated)] // for screen_size
|
||||||
RawInput {
|
RawInput {
|
||||||
mouse_down: self.mouse_down,
|
|
||||||
mouse_pos: self.mouse_pos,
|
|
||||||
scroll_delta: std::mem::take(&mut self.scroll_delta),
|
scroll_delta: std::mem::take(&mut self.scroll_delta),
|
||||||
screen_size: self.screen_size,
|
screen_size: self.screen_size,
|
||||||
screen_rect: self.screen_rect,
|
screen_rect: self.screen_rect,
|
||||||
|
@ -107,8 +96,38 @@ pub enum Event {
|
||||||
pressed: bool,
|
pressed: bool,
|
||||||
modifiers: Modifiers,
|
modifiers: Modifiers,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
PointerMoved(Pos2),
|
||||||
|
PointerButton {
|
||||||
|
pos: Pos2,
|
||||||
|
button: PointerButton,
|
||||||
|
pressed: bool,
|
||||||
|
/// The state of the modifier keys at the time of the event
|
||||||
|
modifiers: Modifiers,
|
||||||
|
},
|
||||||
|
/// The mouse left the screen, or the last/primary touch input disappeared.
|
||||||
|
///
|
||||||
|
/// This means there is no longer a cursor on the screen for hovering etc.
|
||||||
|
///
|
||||||
|
/// On touch-up first send `PointerButton{pressed: false, …}` followed by `PointerLeft`.
|
||||||
|
PointerGone,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mouse button (or similar for touch input)
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub enum PointerButton {
|
||||||
|
/// The primary mouse button is usually the left one.
|
||||||
|
Primary = 0,
|
||||||
|
/// The secondary mouse button is usually the right one,
|
||||||
|
/// and most often used for context menus or other optional things.
|
||||||
|
Secondary = 1,
|
||||||
|
/// The tertiary mouse button is usually the middle mouse button (e.g. clicking the scroll wheel).
|
||||||
|
Middle = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Number of pointer buttons supported by egui, i.e. the number of possible states of [`PointerButton`].
|
||||||
|
pub const NUM_POINTER_BUTTONS: usize = 3;
|
||||||
|
|
||||||
/// State of the modifier keys. These must be fed to egui.
|
/// State of the modifier keys. These must be fed to egui.
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||||
pub struct Modifiers {
|
pub struct Modifiers {
|
||||||
|
@ -208,8 +227,6 @@ impl RawInput {
|
||||||
pub fn ui(&self, ui: &mut crate::Ui) {
|
pub fn ui(&self, ui: &mut crate::Ui) {
|
||||||
#![allow(deprecated)] // for screen_size
|
#![allow(deprecated)] // for screen_size
|
||||||
let Self {
|
let Self {
|
||||||
mouse_down,
|
|
||||||
mouse_pos,
|
|
||||||
scroll_delta,
|
scroll_delta,
|
||||||
screen_size: _,
|
screen_size: _,
|
||||||
screen_rect,
|
screen_rect,
|
||||||
|
@ -220,10 +237,6 @@ impl RawInput {
|
||||||
events,
|
events,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
// TODO: simpler way to show values, e.g. `ui.value("Mouse Pos:", self.mouse_pos);
|
|
||||||
// TODO: `ui.style_mut().text_style = TextStyle::Monospace`;
|
|
||||||
ui.label(format!("mouse_down: {}", mouse_down));
|
|
||||||
ui.label(format!("mouse_pos: {:.1?}", mouse_pos));
|
|
||||||
ui.label(format!("scroll_delta: {:?} points", scroll_delta));
|
ui.label(format!("scroll_delta: {:?} points", scroll_delta));
|
||||||
ui.label(format!("screen_rect: {:?} points", screen_rect));
|
ui.label(format!("screen_rect: {:?} points", screen_rect));
|
||||||
ui.label(format!("pixels_per_point: {:?}", pixels_per_point))
|
ui.label(format!("pixels_per_point: {:?}", pixels_per_point))
|
||||||
|
|
|
@ -5,20 +5,21 @@ use crate::data::input::*;
|
||||||
|
|
||||||
pub use crate::data::input::Key;
|
pub use crate::data::input::Key;
|
||||||
|
|
||||||
/// If mouse moves more than this, it is no longer a click (but maybe a drag)
|
/// If the pointer moves more than this, it is no longer a click (but maybe a drag)
|
||||||
const MAX_CLICK_DIST: f32 = 6.0; // TODO: move to settings
|
const MAX_CLICK_DIST: f32 = 6.0; // TODO: move to settings
|
||||||
/// The new mouse press must come within this many seconds from previous mouse release
|
/// The new pointer press must come within this many seconds from previous pointer release
|
||||||
const MAX_CLICK_DELAY: f64 = 0.3; // TODO: move to settings
|
const MAX_CLICK_DELAY: f64 = 0.3; // TODO: move to settings
|
||||||
|
|
||||||
/// Input state that egui updates each frame.
|
/// Input state that egui updates each frame.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct InputState {
|
pub struct InputState {
|
||||||
/// The raw input we got this frame
|
/// The raw input we got this frame from the backend.
|
||||||
pub raw: RawInput,
|
pub raw: RawInput,
|
||||||
|
|
||||||
pub mouse: CursorState,
|
/// State of the mouse or touch.
|
||||||
|
pub pointer: PointerState,
|
||||||
|
|
||||||
/// How many pixels the user scrolled
|
/// How many pixels the user scrolled.
|
||||||
pub scroll_delta: Vec2,
|
pub scroll_delta: Vec2,
|
||||||
|
|
||||||
/// Position and size of the egui area.
|
/// Position and size of the egui area.
|
||||||
|
@ -52,7 +53,7 @@ impl Default for InputState {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
raw: Default::default(),
|
raw: Default::default(),
|
||||||
mouse: Default::default(),
|
pointer: Default::default(),
|
||||||
scroll_delta: Default::default(),
|
scroll_delta: Default::default(),
|
||||||
screen_rect: Rect::from_min_size(Default::default(), vec2(10_000.0, 10_000.0)),
|
screen_rect: Rect::from_min_size(Default::default(), vec2(10_000.0, 10_000.0)),
|
||||||
pixels_per_point: 1.0,
|
pixels_per_point: 1.0,
|
||||||
|
@ -66,71 +67,6 @@ impl Default for InputState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mouse (or touch) state.
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct CursorState {
|
|
||||||
/// Is the button currently down?
|
|
||||||
/// true the frame when it is pressed,
|
|
||||||
/// false the frame it is released.
|
|
||||||
pub down: bool,
|
|
||||||
|
|
||||||
/// The mouse went from !down to down
|
|
||||||
pub pressed: bool,
|
|
||||||
|
|
||||||
/// The mouse went from down to !down
|
|
||||||
pub released: bool,
|
|
||||||
|
|
||||||
/// If the mouse is down, will it register as a click when released?
|
|
||||||
/// Set to true on mouse down, set to false when mouse moves too much.
|
|
||||||
pub could_be_click: bool,
|
|
||||||
|
|
||||||
/// Was there a click?
|
|
||||||
/// Did a mouse button get released this frame closely after going down?
|
|
||||||
pub click: bool,
|
|
||||||
|
|
||||||
/// Was there a double-click?
|
|
||||||
pub double_click: bool,
|
|
||||||
|
|
||||||
/// When did the mouse get click last?
|
|
||||||
/// Used to check for double-clicks.
|
|
||||||
pub last_click_time: f64,
|
|
||||||
|
|
||||||
/// Current position of the mouse in points.
|
|
||||||
/// None for touch screens when finger is not down.
|
|
||||||
pub pos: Option<Pos2>,
|
|
||||||
|
|
||||||
/// Where did the current click/drag originate?
|
|
||||||
pub press_origin: Option<Pos2>,
|
|
||||||
|
|
||||||
/// How much the mouse moved compared to last frame, in points.
|
|
||||||
pub delta: Vec2,
|
|
||||||
|
|
||||||
/// Current velocity of mouse cursor.
|
|
||||||
pub velocity: Vec2,
|
|
||||||
|
|
||||||
/// Recent movement of the mouse.
|
|
||||||
/// Used for calculating velocity of mouse pointer.
|
|
||||||
pos_history: History<Pos2>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for CursorState {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
down: false,
|
|
||||||
pressed: false,
|
|
||||||
released: false,
|
|
||||||
could_be_click: false,
|
|
||||||
click: false,
|
|
||||||
double_click: false,
|
|
||||||
last_click_time: std::f64::NEG_INFINITY,
|
|
||||||
pos: None,
|
|
||||||
press_origin: None,
|
|
||||||
delta: Vec2::zero(),
|
|
||||||
velocity: Vec2::zero(),
|
|
||||||
pos_history: History::new(1000, 0.1),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl InputState {
|
impl InputState {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn begin_frame(self, new: RawInput) -> InputState {
|
pub fn begin_frame(self, new: RawInput) -> InputState {
|
||||||
|
@ -147,7 +83,7 @@ impl InputState {
|
||||||
self.screen_rect
|
self.screen_rect
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let mouse = self.mouse.begin_frame(time, &new);
|
let pointer = self.pointer.begin_frame(time, &new);
|
||||||
let mut keys_down = self.keys_down;
|
let mut keys_down = self.keys_down;
|
||||||
for event in &new.events {
|
for event in &new.events {
|
||||||
if let Event::Key { key, pressed, .. } = event {
|
if let Event::Key { key, pressed, .. } = event {
|
||||||
|
@ -159,7 +95,7 @@ impl InputState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InputState {
|
InputState {
|
||||||
mouse,
|
pointer,
|
||||||
scroll_delta: new.scroll_delta,
|
scroll_delta: new.scroll_delta,
|
||||||
screen_rect,
|
screen_rect,
|
||||||
pixels_per_point: new.pixels_per_point.unwrap_or(self.pixels_per_point),
|
pixels_per_point: new.pixels_per_point.unwrap_or(self.pixels_per_point),
|
||||||
|
@ -178,11 +114,7 @@ impl InputState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wants_repaint(&self) -> bool {
|
pub fn wants_repaint(&self) -> bool {
|
||||||
self.mouse.pressed
|
self.pointer.wants_repaint() || self.scroll_delta != Vec2::zero() || !self.events.is_empty()
|
||||||
|| self.mouse.released
|
|
||||||
|| self.mouse.delta != Vec2::zero()
|
|
||||||
|| self.scroll_delta != Vec2::zero()
|
|
||||||
|| !self.events.is_empty()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Was the given key pressed this frame?
|
/// Was the given key pressed this frame?
|
||||||
|
@ -236,85 +168,315 @@ impl InputState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CursorState {
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// A pointer (mouse or touch) click.
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub(crate) struct Click {
|
||||||
|
pub pos: Pos2,
|
||||||
|
pub button: PointerButton,
|
||||||
|
/// 1 or 2 (double-click)
|
||||||
|
pub count: u32,
|
||||||
|
/// Allows you to check for e.g. shift-click
|
||||||
|
pub modifiers: Modifiers,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Click {
|
||||||
|
pub fn is_double(&self) -> bool {
|
||||||
|
self.count == 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub(crate) enum PointerEvent {
|
||||||
|
Moved(Pos2),
|
||||||
|
Pressed(Pos2),
|
||||||
|
Released(Option<Click>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PointerEvent {
|
||||||
|
pub fn is_press(&self) -> bool {
|
||||||
|
matches!(self, PointerEvent::Pressed(_))
|
||||||
|
}
|
||||||
|
pub fn is_release(&self) -> bool {
|
||||||
|
matches!(self, PointerEvent::Released(_))
|
||||||
|
}
|
||||||
|
pub fn is_click(&self) -> bool {
|
||||||
|
matches!(self, PointerEvent::Released(Some(_click)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mouse or touch state.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct PointerState {
|
||||||
|
// Consider a finger tapping a touch screen.
|
||||||
|
// What position should we report?
|
||||||
|
// The location of the touch, or `None`, because the finger is gone?
|
||||||
|
//
|
||||||
|
// For some cases we want the first: e.g. to check for interaction.
|
||||||
|
// For showing tooltips, we want the latter (no tooltips, since there are no fingers).
|
||||||
|
/// Latest reported pointer position.
|
||||||
|
/// When tapping a touch screen, this will be `None`.
|
||||||
|
latest_pos: Option<Pos2>,
|
||||||
|
|
||||||
|
/// Latest position of the mouse, but ignoring any [`Event::PointerGone`]
|
||||||
|
/// if there were interactions this frame.
|
||||||
|
/// When tapping a touch screen, this will be the location of the touch.
|
||||||
|
interact_pos: Option<Pos2>,
|
||||||
|
|
||||||
|
/// How much the pointer moved compared to last frame, in points.
|
||||||
|
delta: Vec2,
|
||||||
|
|
||||||
|
/// Current velocity of pointer.
|
||||||
|
velocity: Vec2,
|
||||||
|
|
||||||
|
/// Recent movement of the pointer.
|
||||||
|
/// Used for calculating velocity of pointer.
|
||||||
|
pos_history: History<Pos2>,
|
||||||
|
|
||||||
|
down: [bool; NUM_POINTER_BUTTONS],
|
||||||
|
|
||||||
|
/// Where did the current click/drag originate?
|
||||||
|
/// `None` if no mouse button is down.
|
||||||
|
press_origin: Option<Pos2>,
|
||||||
|
|
||||||
|
/// If the pointer button is down, will it register as a click when released?
|
||||||
|
/// Set to true on pointer button down, set to false when pointer button moves too much.
|
||||||
|
could_be_click: bool,
|
||||||
|
|
||||||
|
/// When did the pointer get click last?
|
||||||
|
/// Used to check for double-clicks.
|
||||||
|
last_click_time: f64,
|
||||||
|
|
||||||
|
// /// All clicks that occurred this frame
|
||||||
|
// clicks: Vec<Click>,
|
||||||
|
/// All button events that occurred this frame
|
||||||
|
pub(crate) pointer_events: Vec<PointerEvent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PointerState {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
latest_pos: None,
|
||||||
|
interact_pos: None,
|
||||||
|
delta: Vec2::zero(),
|
||||||
|
velocity: Vec2::zero(),
|
||||||
|
pos_history: History::new(1000, 0.1),
|
||||||
|
down: Default::default(),
|
||||||
|
press_origin: None,
|
||||||
|
could_be_click: false,
|
||||||
|
last_click_time: std::f64::NEG_INFINITY,
|
||||||
|
pointer_events: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PointerState {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn begin_frame(mut self, time: f64, new: &RawInput) -> CursorState {
|
pub(crate) fn begin_frame(mut self, time: f64, new: &RawInput) -> PointerState {
|
||||||
let delta = new
|
self.pointer_events.clear();
|
||||||
.mouse_pos
|
|
||||||
.and_then(|new| self.pos.map(|last| new - last))
|
|
||||||
.unwrap_or_default();
|
|
||||||
let pressed = !self.down && new.mouse_down;
|
|
||||||
|
|
||||||
let released = self.down && !new.mouse_down;
|
let old_pos = self.latest_pos;
|
||||||
let click = released && self.could_be_click;
|
self.interact_pos = self.latest_pos;
|
||||||
let double_click = click && (time - self.last_click_time) < MAX_CLICK_DELAY;
|
|
||||||
let mut press_origin = self.press_origin;
|
for event in &new.events {
|
||||||
let mut could_be_click = self.could_be_click;
|
match event {
|
||||||
let mut last_click_time = self.last_click_time;
|
Event::PointerMoved(pos) => {
|
||||||
if click {
|
let pos = *pos;
|
||||||
last_click_time = time
|
|
||||||
|
self.latest_pos = Some(pos);
|
||||||
|
self.interact_pos = Some(pos);
|
||||||
|
|
||||||
|
if let Some(press_origin) = &mut self.press_origin {
|
||||||
|
self.could_be_click &= press_origin.distance(pos) < MAX_CLICK_DIST;
|
||||||
|
} else {
|
||||||
|
self.could_be_click = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.pointer_events.push(PointerEvent::Moved(pos));
|
||||||
|
}
|
||||||
|
Event::PointerButton {
|
||||||
|
pos,
|
||||||
|
button,
|
||||||
|
pressed,
|
||||||
|
modifiers,
|
||||||
|
} => {
|
||||||
|
let pos = *pos;
|
||||||
|
let button = *button;
|
||||||
|
let pressed = *pressed;
|
||||||
|
let modifiers = *modifiers;
|
||||||
|
|
||||||
|
self.latest_pos = Some(pos);
|
||||||
|
self.interact_pos = Some(pos);
|
||||||
|
|
||||||
|
if pressed {
|
||||||
|
// Start of a drag: we want to track the velocity for during the drag
|
||||||
|
// and ignore any incoming movement
|
||||||
|
self.pos_history.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if pressed {
|
||||||
|
self.press_origin = Some(pos);
|
||||||
|
self.could_be_click = true;
|
||||||
|
self.pointer_events.push(PointerEvent::Pressed(pos));
|
||||||
|
} else {
|
||||||
|
let clicked = self.could_be_click;
|
||||||
|
|
||||||
|
let click = if clicked {
|
||||||
|
let double_click = (time - self.last_click_time) < MAX_CLICK_DELAY;
|
||||||
|
let count = if double_click { 2 } else { 1 };
|
||||||
|
|
||||||
|
self.last_click_time = time;
|
||||||
|
|
||||||
|
Some(Click {
|
||||||
|
pos,
|
||||||
|
button,
|
||||||
|
count,
|
||||||
|
modifiers,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
self.pointer_events.push(PointerEvent::Released(click));
|
||||||
|
|
||||||
|
self.press_origin = None;
|
||||||
|
self.could_be_click = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.down[button as usize] = pressed;
|
||||||
|
}
|
||||||
|
Event::PointerGone => {
|
||||||
|
self.latest_pos = None;
|
||||||
|
// NOTE: we do NOT clear `self.interact_pos` here. It will be cleared next frame.
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if pressed {
|
self.delta = if let (Some(old_pos), Some(new_pos)) = (old_pos, self.latest_pos) {
|
||||||
press_origin = new.mouse_pos;
|
new_pos - old_pos
|
||||||
could_be_click = true;
|
|
||||||
} else if !self.down || self.pos.is_none() {
|
|
||||||
press_origin = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let (Some(press_origin), Some(mouse_pos)) = (new.mouse_pos, press_origin) {
|
|
||||||
could_be_click &= press_origin.distance(mouse_pos) < MAX_CLICK_DIST;
|
|
||||||
} else {
|
} else {
|
||||||
could_be_click = false;
|
Vec2::zero()
|
||||||
}
|
};
|
||||||
|
|
||||||
if pressed {
|
if let Some(pos) = self.latest_pos {
|
||||||
// Start of a drag: we want to track the velocity for during the drag
|
self.pos_history.add(time, pos);
|
||||||
// and ignore any incoming movement
|
|
||||||
self.pos_history.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(mouse_pos) = new.mouse_pos {
|
|
||||||
self.pos_history.add(time, mouse_pos);
|
|
||||||
} else {
|
} else {
|
||||||
// we do not clear the `mouse_tracker` here, because it is exactly when a finger has
|
// we do not clear the `pos_history` here, because it is exactly when a finger has
|
||||||
// released from the touch screen that we may want to assign a velocity to whatever
|
// released from the touch screen that we may want to assign a velocity to whatever
|
||||||
// the user tried to throw
|
// the user tried to throw.
|
||||||
}
|
}
|
||||||
|
|
||||||
self.pos_history.flush(time);
|
self.pos_history.flush(time);
|
||||||
let velocity = if self.pos_history.len() >= 3 && self.pos_history.duration() > 0.01 {
|
|
||||||
|
self.velocity = if self.pos_history.len() >= 3 && self.pos_history.duration() > 0.01 {
|
||||||
self.pos_history.velocity().unwrap_or_default()
|
self.pos_history.velocity().unwrap_or_default()
|
||||||
} else {
|
} else {
|
||||||
Vec2::default()
|
Vec2::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
CursorState {
|
self
|
||||||
down: new.mouse_down && new.mouse_pos.is_some(),
|
|
||||||
pressed,
|
|
||||||
released,
|
|
||||||
could_be_click,
|
|
||||||
click,
|
|
||||||
double_click,
|
|
||||||
last_click_time,
|
|
||||||
pos: new.mouse_pos,
|
|
||||||
press_origin,
|
|
||||||
delta,
|
|
||||||
velocity,
|
|
||||||
pos_history: self.pos_history,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn wants_repaint(&self) -> bool {
|
||||||
|
!self.pointer_events.is_empty() || self.delta != Vec2::zero()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// How much the pointer moved compared to last frame, in points.
|
||||||
|
pub fn delta(&self) -> Vec2 {
|
||||||
|
self.delta
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Current velocity of pointer.
|
||||||
|
pub fn velocity(&self) -> Vec2 {
|
||||||
|
self.velocity
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Where did the current click/drag originate?
|
||||||
|
/// `None` if no mouse button is down.
|
||||||
|
pub fn press_origin(&self) -> Option<Pos2> {
|
||||||
|
self.press_origin
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Latest reported pointer position.
|
||||||
|
/// When tapping a touch screen, this will be `None`.
|
||||||
|
pub(crate) fn latest_pos(&self) -> Option<Pos2> {
|
||||||
|
self.latest_pos
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If it is a good idea to show a tooltip, where is pointer?
|
||||||
|
pub fn tooltip_pos(&self) -> Option<Pos2> {
|
||||||
|
self.latest_pos
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If you detect a click or drag and wants to know where it happened, use this.
|
||||||
|
///
|
||||||
|
/// Latest position of the mouse, but ignoring any [`Event::PointerGone`]
|
||||||
|
/// if there were interactions this frame.
|
||||||
|
/// When tapping a touch screen, this will be the location of the touch.
|
||||||
|
pub fn interact_pos(&self) -> Option<Pos2> {
|
||||||
|
self.interact_pos
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Do we have a pointer?
|
||||||
|
///
|
||||||
|
/// `false` if the mouse is not over the egui area, or if no touches are down on touch screens.
|
||||||
|
pub fn has_pointer(&self) -> bool {
|
||||||
|
self.latest_pos.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Is the pointer currently moving?
|
||||||
|
/// This is smoothed so a few frames of stillness is required before this returns `true`.
|
||||||
pub fn is_moving(&self) -> bool {
|
pub fn is_moving(&self) -> bool {
|
||||||
self.velocity != Vec2::zero()
|
self.velocity != Vec2::zero()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Was any pointer button pressed (`!down -> down`) this frame?
|
||||||
|
/// This can sometimes return `true` even if `any_down() == false`
|
||||||
|
/// because a press can be shorted than one frame.
|
||||||
|
pub fn any_pressed(&self) -> bool {
|
||||||
|
self.pointer_events.iter().any(|event| event.is_press())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Was any pointer button released (`down -> !down`) this frame?
|
||||||
|
pub fn any_released(&self) -> bool {
|
||||||
|
self.pointer_events.iter().any(|event| event.is_release())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Is any pointer button currently down?
|
||||||
|
pub fn any_down(&self) -> bool {
|
||||||
|
self.down.iter().any(|&down| down)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Were there any type of click this frame?
|
||||||
|
pub fn any_click(&self) -> bool {
|
||||||
|
self.pointer_events.iter().any(|event| event.is_click())
|
||||||
|
}
|
||||||
|
|
||||||
|
// /// Was this button pressed (`!down -> down`) this frame?
|
||||||
|
// /// This can sometimes return `true` even if `any_down() == false`
|
||||||
|
// /// because a press can be shorted than one frame.
|
||||||
|
// pub fn button_pressed(&self, button: PointerButton) -> bool {
|
||||||
|
// self.pointer_events.iter().any(|event| event.is_press())
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /// Was this button released (`down -> !down`) this frame?
|
||||||
|
// pub fn button_released(&self, button: PointerButton) -> bool {
|
||||||
|
// self.pointer_events.iter().any(|event| event.is_release())
|
||||||
|
// }
|
||||||
|
|
||||||
|
/// Is this button currently down?
|
||||||
|
pub fn button_down(&self, button: PointerButton) -> bool {
|
||||||
|
self.down[button as usize]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InputState {
|
impl InputState {
|
||||||
pub fn ui(&self, ui: &mut crate::Ui) {
|
pub fn ui(&self, ui: &mut crate::Ui) {
|
||||||
let Self {
|
let Self {
|
||||||
raw,
|
raw,
|
||||||
mouse,
|
pointer,
|
||||||
scroll_delta,
|
scroll_delta,
|
||||||
screen_rect,
|
screen_rect,
|
||||||
pixels_per_point,
|
pixels_per_point,
|
||||||
|
@ -329,10 +491,10 @@ impl InputState {
|
||||||
ui.style_mut().body_text_style = crate::paint::TextStyle::Monospace;
|
ui.style_mut().body_text_style = crate::paint::TextStyle::Monospace;
|
||||||
ui.collapsing("Raw Input", |ui| raw.ui(ui));
|
ui.collapsing("Raw Input", |ui| raw.ui(ui));
|
||||||
|
|
||||||
crate::containers::CollapsingHeader::new("🖱 Mouse")
|
crate::containers::CollapsingHeader::new("🖱 Pointer")
|
||||||
.default_open(true)
|
.default_open(true)
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
mouse.ui(ui);
|
pointer.ui(ui);
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.label(format!("scroll_delta: {:?} points", scroll_delta));
|
ui.label(format!("scroll_delta: {:?} points", scroll_delta));
|
||||||
|
@ -354,36 +516,32 @@ impl InputState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CursorState {
|
impl PointerState {
|
||||||
pub fn ui(&self, ui: &mut crate::Ui) {
|
pub fn ui(&self, ui: &mut crate::Ui) {
|
||||||
let Self {
|
let Self {
|
||||||
down,
|
latest_pos,
|
||||||
pressed,
|
interact_pos,
|
||||||
released,
|
|
||||||
could_be_click,
|
|
||||||
click,
|
|
||||||
double_click,
|
|
||||||
last_click_time,
|
|
||||||
pos,
|
|
||||||
press_origin,
|
|
||||||
delta,
|
delta,
|
||||||
velocity,
|
velocity,
|
||||||
pos_history: _,
|
pos_history: _,
|
||||||
|
down,
|
||||||
|
press_origin,
|
||||||
|
could_be_click,
|
||||||
|
last_click_time,
|
||||||
|
pointer_events,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
ui.label(format!("down: {}", down));
|
ui.label(format!("latest_pos: {:?}", latest_pos));
|
||||||
ui.label(format!("pressed: {}", pressed));
|
ui.label(format!("interact_pos: {:?}", interact_pos));
|
||||||
ui.label(format!("released: {}", released));
|
|
||||||
ui.label(format!("could_be_click: {}", could_be_click));
|
|
||||||
ui.label(format!("click: {}", click));
|
|
||||||
ui.label(format!("double_click: {}", double_click));
|
|
||||||
ui.label(format!("last_click_time: {:.3}", last_click_time));
|
|
||||||
ui.label(format!("pos: {:?}", pos));
|
|
||||||
ui.label(format!("press_origin: {:?}", press_origin));
|
|
||||||
ui.label(format!("delta: {:?}", delta));
|
ui.label(format!("delta: {:?}", delta));
|
||||||
ui.label(format!(
|
ui.label(format!(
|
||||||
"velocity: [{:3.0} {:3.0}] points/sec",
|
"velocity: [{:3.0} {:3.0}] points/sec",
|
||||||
velocity.x, velocity.y
|
velocity.x, velocity.y
|
||||||
));
|
));
|
||||||
|
ui.label(format!("down: {:#?}", down));
|
||||||
|
ui.label(format!("press_origin: {:?}", press_origin));
|
||||||
|
ui.label(format!("could_be_click: {:#?}", could_be_click));
|
||||||
|
ui.label(format!("last_click_time: {:#?}", last_click_time));
|
||||||
|
ui.label(format!("pointer_events: {:?}", pointer_events));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,12 +29,10 @@ impl Widget for &epaint::Texture {
|
||||||
|
|
||||||
let (tex_w, tex_h) = (self.width as f32, self.height as f32);
|
let (tex_w, tex_h) = (self.width as f32, self.height as f32);
|
||||||
|
|
||||||
|
let pointer_pos = response.interact_pointer_pos();
|
||||||
|
|
||||||
response.on_hover_ui(|ui| {
|
response.on_hover_ui(|ui| {
|
||||||
let pos = ui
|
let pos = pointer_pos.unwrap_or_else(|| ui.min_rect().left_top());
|
||||||
.input()
|
|
||||||
.mouse
|
|
||||||
.pos
|
|
||||||
.unwrap_or_else(|| ui.min_rect().left_top());
|
|
||||||
let (_id, zoom_rect) = ui.allocate_space(vec2(128.0, 128.0));
|
let (_id, zoom_rect) = ui.allocate_space(vec2(128.0, 128.0));
|
||||||
let u = remap_clamp(pos.x, rect.x_range(), 0.0..=tex_w);
|
let u = remap_clamp(pos.x, rect.x_range(), 0.0..=tex_w);
|
||||||
let v = remap_clamp(pos.y, rect.y_range(), 0.0..=tex_h);
|
let v = remap_clamp(pos.y, rect.y_range(), 0.0..=tex_h);
|
||||||
|
|
|
@ -125,7 +125,8 @@ pub(crate) struct Interaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Interaction {
|
impl Interaction {
|
||||||
pub fn is_using_mouse(&self) -> bool {
|
/// Are we currently clicking or dragging an egui widget?
|
||||||
|
pub fn is_using_pointer(&self) -> bool {
|
||||||
self.click_id.is_some() || self.drag_id.is_some()
|
self.click_id.is_some() || self.drag_id.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,12 +139,8 @@ impl Interaction {
|
||||||
self.click_interest = false;
|
self.click_interest = false;
|
||||||
self.drag_interest = false;
|
self.drag_interest = false;
|
||||||
|
|
||||||
if !prev_input.mouse.could_be_click {
|
if !prev_input.pointer.any_down() || prev_input.pointer.latest_pos().is_none() {
|
||||||
self.click_id = None;
|
// pointer button was not down last frame
|
||||||
}
|
|
||||||
|
|
||||||
if !prev_input.mouse.down || prev_input.mouse.pos.is_none() {
|
|
||||||
// mouse was not down last frame
|
|
||||||
self.click_id = None;
|
self.click_id = None;
|
||||||
self.drag_id = None;
|
self.drag_id = None;
|
||||||
}
|
}
|
||||||
|
@ -175,7 +172,7 @@ impl Memory {
|
||||||
) {
|
) {
|
||||||
self.interaction.begin_frame(prev_input, new_input);
|
self.interaction.begin_frame(prev_input, new_input);
|
||||||
|
|
||||||
if !prev_input.mouse.down {
|
if !prev_input.pointer.any_down() {
|
||||||
self.window_interaction = None;
|
self.window_interaction = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
//!
|
//!
|
||||||
//! menu::bar(ui, |ui| {
|
//! menu::bar(ui, |ui| {
|
||||||
//! menu::menu(ui, "File", |ui| {
|
//! menu::menu(ui, "File", |ui| {
|
||||||
//! if ui.button("Open").clicked {
|
//! if ui.button("Open").clicked() {
|
||||||
//! // ...
|
//! // ...
|
||||||
//! }
|
//! }
|
||||||
//! });
|
//! });
|
||||||
|
@ -83,14 +83,14 @@ fn menu_impl<'c>(
|
||||||
}
|
}
|
||||||
|
|
||||||
let button_response = ui.add(button);
|
let button_response = ui.add(button);
|
||||||
if button_response.clicked {
|
if button_response.clicked() {
|
||||||
// Toggle
|
// Toggle
|
||||||
if bar_state.open_menu == Some(menu_id) {
|
if bar_state.open_menu == Some(menu_id) {
|
||||||
bar_state.open_menu = None;
|
bar_state.open_menu = None;
|
||||||
} else {
|
} else {
|
||||||
bar_state.open_menu = Some(menu_id);
|
bar_state.open_menu = Some(menu_id);
|
||||||
}
|
}
|
||||||
} else if button_response.hovered && bar_state.open_menu.is_some() {
|
} else if button_response.hovered() && bar_state.open_menu.is_some() {
|
||||||
bar_state.open_menu = Some(menu_id);
|
bar_state.open_menu = Some(menu_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,7 +116,8 @@ fn menu_impl<'c>(
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: this prevents sub-menus in menus. We should fix that.
|
// TODO: this prevents sub-menus in menus. We should fix that.
|
||||||
if ui.input().key_pressed(Key::Escape) || ui.input().mouse.click && !button_response.clicked
|
if ui.input().key_pressed(Key::Escape)
|
||||||
|
|| ui.input().pointer.any_click() && !button_response.clicked()
|
||||||
{
|
{
|
||||||
bar_state.open_menu = None;
|
bar_state.open_menu = None;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
use crate::math::{lerp, Align, Rect};
|
use crate::{
|
||||||
|
math::{lerp, Align, Pos2, Rect},
|
||||||
|
PointerButton, NUM_POINTER_BUTTONS,
|
||||||
|
};
|
||||||
use crate::{CtxRef, Id, LayerId, Sense, Ui};
|
use crate::{CtxRef, Id, LayerId, Sense, Ui};
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
@ -27,35 +30,34 @@ pub struct Response {
|
||||||
pub sense: Sense,
|
pub sense: Sense,
|
||||||
|
|
||||||
// OUT:
|
// OUT:
|
||||||
/// The mouse is hovering above this.
|
/// The pointer is hovering above this widget or the widget was clicked/tapped this frame.
|
||||||
pub hovered: bool,
|
pub(crate) hovered: bool,
|
||||||
|
|
||||||
/// The mouse clicked this thing this frame.
|
/// The pointer clicked this thing this frame.
|
||||||
pub clicked: bool,
|
pub(crate) clicked: [bool; NUM_POINTER_BUTTONS],
|
||||||
|
|
||||||
/// The thing was double-clicked.
|
/// The thing was double-clicked.
|
||||||
pub double_clicked: bool,
|
pub(crate) double_clicked: [bool; NUM_POINTER_BUTTONS],
|
||||||
|
|
||||||
/// The mouse is interacting with this thing (e.g. dragging it).
|
/// The widgets is being dragged
|
||||||
pub active: bool,
|
pub(crate) dragged: bool,
|
||||||
|
|
||||||
|
/// The widget was being dragged, but now it has been released.
|
||||||
|
pub(crate) drag_released: bool,
|
||||||
|
|
||||||
|
/// Is the pointer button currently down on this widget?
|
||||||
|
/// This is true if the pointer is pressing down or dragging a widget
|
||||||
|
pub(crate) is_pointer_button_down_on: bool,
|
||||||
|
|
||||||
|
/// Where the pointer (mouse/touch) were when when this widget was clicked or dragged.
|
||||||
|
/// `None` if the widget is not being interacted with.
|
||||||
|
pub(crate) interact_pointer_pos: Option<Pos2>,
|
||||||
|
|
||||||
/// This widget has the keyboard focus (i.e. is receiving key pressed).
|
/// This widget has the keyboard focus (i.e. is receiving key pressed).
|
||||||
pub has_kb_focus: bool,
|
pub(crate) has_kb_focus: bool,
|
||||||
|
|
||||||
/// The widget had keyboard focus and lost it,
|
/// The widget had keyboard focus and lost it.
|
||||||
/// perhaps because the user pressed enter.
|
pub(crate) lost_kb_focus: bool,
|
||||||
/// If you want to do an action when a user presses enter in a text field,
|
|
||||||
/// use this.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// # let mut ui = egui::Ui::__test();
|
|
||||||
/// # let mut my_text = String::new();
|
|
||||||
/// # fn do_request(_: &str) {}
|
|
||||||
/// if ui.text_edit_singleline(&mut my_text).lost_kb_focus {
|
|
||||||
/// do_request(&my_text);
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub lost_kb_focus: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for Response {
|
impl std::fmt::Debug for Response {
|
||||||
|
@ -69,7 +71,10 @@ impl std::fmt::Debug for Response {
|
||||||
hovered,
|
hovered,
|
||||||
clicked,
|
clicked,
|
||||||
double_clicked,
|
double_clicked,
|
||||||
active,
|
dragged,
|
||||||
|
drag_released,
|
||||||
|
is_pointer_button_down_on,
|
||||||
|
interact_pointer_pos,
|
||||||
has_kb_focus,
|
has_kb_focus,
|
||||||
lost_kb_focus,
|
lost_kb_focus,
|
||||||
} = self;
|
} = self;
|
||||||
|
@ -81,7 +86,10 @@ impl std::fmt::Debug for Response {
|
||||||
.field("hovered", hovered)
|
.field("hovered", hovered)
|
||||||
.field("clicked", clicked)
|
.field("clicked", clicked)
|
||||||
.field("double_clicked", double_clicked)
|
.field("double_clicked", double_clicked)
|
||||||
.field("active", active)
|
.field("dragged", dragged)
|
||||||
|
.field("drag_released", drag_released)
|
||||||
|
.field("is_pointer_button_down_on", is_pointer_button_down_on)
|
||||||
|
.field("interact_pointer_pos", interact_pointer_pos)
|
||||||
.field("has_kb_focus", has_kb_focus)
|
.field("has_kb_focus", has_kb_focus)
|
||||||
.field("lost_kb_focus", lost_kb_focus)
|
.field("lost_kb_focus", lost_kb_focus)
|
||||||
.finish()
|
.finish()
|
||||||
|
@ -89,10 +97,89 @@ impl std::fmt::Debug for Response {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Response {
|
impl Response {
|
||||||
|
/// Returns true if this widget was clicked this frame by the primary button.
|
||||||
|
pub fn clicked(&self) -> bool {
|
||||||
|
self.clicked[PointerButton::Primary as usize]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if this widget was clicked this frame by the secondary mouse button (e.g. the right mouse button).
|
||||||
|
pub fn secondary_clicked(&self) -> bool {
|
||||||
|
self.clicked[PointerButton::Secondary as usize]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if this widget was clicked this frame by the middle mouse button.
|
||||||
|
pub fn middle_clicked(&self) -> bool {
|
||||||
|
self.clicked[PointerButton::Middle as usize]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if this widget was double-clicked this frame by the primary button.
|
||||||
|
pub fn double_clicked(&self) -> bool {
|
||||||
|
self.double_clicked[PointerButton::Primary as usize]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The pointer is hovering above this widget or the widget was clicked/tapped this frame.
|
||||||
|
pub fn hovered(&self) -> bool {
|
||||||
|
self.hovered
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The widget had keyboard focus and lost it,
|
||||||
|
/// perhaps because the user pressed enter.
|
||||||
|
/// If you want to do an action when a user presses enter in a text field,
|
||||||
|
/// use this.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # let mut ui = egui::Ui::__test();
|
||||||
|
/// # let mut my_text = String::new();
|
||||||
|
/// # fn do_request(_: &str) {}
|
||||||
|
/// if ui.text_edit_singleline(&mut my_text).lost_kb_focus() {
|
||||||
|
/// do_request(&my_text);
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn lost_kb_focus(&self) -> bool {
|
||||||
|
self.lost_kb_focus
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The widgets is being dragged.
|
||||||
|
///
|
||||||
|
/// To find out which button(s), query [`PointerState::button_down`]
|
||||||
|
/// (`ui.input().pointer.button_down(…)`).
|
||||||
|
pub fn dragged(&self) -> bool {
|
||||||
|
self.dragged
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The widget was being dragged, but now it has been released.
|
||||||
|
pub fn drag_released(&self) -> bool {
|
||||||
|
self.drag_released
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if this widget was clicked this frame by the given button.
|
||||||
|
pub fn clicked_by(&self, button: PointerButton) -> bool {
|
||||||
|
self.clicked[button as usize]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if this widget was double-clicked this frame by the given button.
|
||||||
|
pub fn double_clicked_by(&self, button: PointerButton) -> bool {
|
||||||
|
self.double_clicked[button as usize]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Where the pointer (mouse/touch) were when when this widget was clicked or dragged.
|
||||||
|
/// `None` if the widget is not being interacted with.
|
||||||
|
pub fn interact_pointer_pos(&self) -> Option<Pos2> {
|
||||||
|
self.interact_pointer_pos
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Is the pointer button currently down on this widget?
|
||||||
|
/// This is true if the pointer is pressing down or dragging a widget
|
||||||
|
pub fn is_pointer_button_down_on(&self) -> bool {
|
||||||
|
self.is_pointer_button_down_on
|
||||||
|
}
|
||||||
|
|
||||||
/// Show this UI if the item was hovered (i.e. a tooltip).
|
/// Show this UI if the item was hovered (i.e. a tooltip).
|
||||||
/// If you call this multiple times the tooltips will stack underneath the previous ones.
|
/// If you call this multiple times the tooltips will stack underneath the previous ones.
|
||||||
pub fn on_hover_ui(self, add_contents: impl FnOnce(&mut Ui)) -> Self {
|
pub fn on_hover_ui(self, add_contents: impl FnOnce(&mut Ui)) -> Self {
|
||||||
if self.hovered || self.ctx.memory().everything_is_visible() {
|
if (self.hovered() && self.ctx.input().pointer.tooltip_pos().is_some())
|
||||||
|
|| self.ctx.memory().everything_is_visible()
|
||||||
|
{
|
||||||
crate::containers::show_tooltip(&self.ctx, add_contents);
|
crate::containers::show_tooltip(&self.ctx, add_contents);
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
|
@ -116,9 +203,9 @@ impl Response {
|
||||||
/// ```
|
/// ```
|
||||||
/// # let mut ui = egui::Ui::__test();
|
/// # let mut ui = egui::Ui::__test();
|
||||||
/// let response = ui.label("hello");
|
/// let response = ui.label("hello");
|
||||||
/// assert!(!response.clicked); // labels don't sense clicks
|
/// assert!(!response.clicked()); // labels don't sense clicks
|
||||||
/// let response = response.interact(egui::Sense::click());
|
/// let response = response.interact(egui::Sense::click());
|
||||||
/// if response.clicked { /* … */ }
|
/// if response.clicked() { /* … */ }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn interact(&self, sense: Sense) -> Self {
|
pub fn interact(&self, sense: Sense) -> Self {
|
||||||
self.ctx
|
self.ctx
|
||||||
|
@ -133,7 +220,7 @@ impl Response {
|
||||||
/// egui::ScrollArea::auto_sized().show(ui, |ui| {
|
/// egui::ScrollArea::auto_sized().show(ui, |ui| {
|
||||||
/// for i in 0..1000 {
|
/// for i in 0..1000 {
|
||||||
/// let response = ui.button(format!("Button {}", i));
|
/// let response = ui.button(format!("Button {}", i));
|
||||||
/// if response.clicked {
|
/// if response.clicked() {
|
||||||
/// response.scroll_to_me(Align::Center);
|
/// response.scroll_to_me(Align::Center);
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
|
@ -161,9 +248,21 @@ impl Response {
|
||||||
rect: self.rect.union(other.rect),
|
rect: self.rect.union(other.rect),
|
||||||
sense: self.sense.union(other.sense),
|
sense: self.sense.union(other.sense),
|
||||||
hovered: self.hovered || other.hovered,
|
hovered: self.hovered || other.hovered,
|
||||||
clicked: self.clicked || other.clicked,
|
clicked: [
|
||||||
double_clicked: self.double_clicked || other.double_clicked,
|
self.clicked[0] || other.clicked[0],
|
||||||
active: self.active || other.active,
|
self.clicked[1] || other.clicked[1],
|
||||||
|
self.clicked[2] || other.clicked[2],
|
||||||
|
],
|
||||||
|
double_clicked: [
|
||||||
|
self.double_clicked[0] || other.double_clicked[0],
|
||||||
|
self.double_clicked[1] || other.double_clicked[1],
|
||||||
|
self.double_clicked[2] || other.double_clicked[2],
|
||||||
|
],
|
||||||
|
dragged: self.dragged || other.dragged,
|
||||||
|
drag_released: self.drag_released || other.drag_released,
|
||||||
|
is_pointer_button_down_on: self.is_pointer_button_down_on
|
||||||
|
|| other.is_pointer_button_down_on,
|
||||||
|
interact_pointer_pos: self.interact_pointer_pos.or(other.interact_pointer_pos),
|
||||||
has_kb_focus: self.has_kb_focus || other.has_kb_focus,
|
has_kb_focus: self.has_kb_focus || other.has_kb_focus,
|
||||||
lost_kb_focus: self.lost_kb_focus || other.lost_kb_focus,
|
lost_kb_focus: self.lost_kb_focus || other.lost_kb_focus,
|
||||||
}
|
}
|
||||||
|
@ -195,7 +294,7 @@ impl std::ops::BitOr for Response {
|
||||||
/// let mut response = ui.add(widget_a);
|
/// let mut response = ui.add(widget_a);
|
||||||
/// response |= ui.add(widget_b);
|
/// response |= ui.add(widget_b);
|
||||||
/// response |= ui.add(widget_c);
|
/// response |= ui.add(widget_c);
|
||||||
/// if response.active { ui.label("You are interacting with one of the widgets"); }
|
/// if response.hovered() { ui.label("You hovered at least one of the widgets"); }
|
||||||
/// ```
|
/// ```
|
||||||
impl std::ops::BitOrAssign for Response {
|
impl std::ops::BitOrAssign for Response {
|
||||||
fn bitor_assign(&mut self, rhs: Self) {
|
fn bitor_assign(&mut self, rhs: Self) {
|
||||||
|
|
|
@ -195,11 +195,11 @@ pub struct Widgets {
|
||||||
|
|
||||||
impl Widgets {
|
impl Widgets {
|
||||||
pub fn style(&self, response: &Response) -> &WidgetVisuals {
|
pub fn style(&self, response: &Response) -> &WidgetVisuals {
|
||||||
if response.active || response.has_kb_focus {
|
if response.is_pointer_button_down_on() || response.has_kb_focus {
|
||||||
&self.active
|
&self.active
|
||||||
} else if response.sense == crate::Sense::hover() {
|
} else if response.sense == crate::Sense::hover() {
|
||||||
&self.disabled
|
&self.disabled
|
||||||
} else if response.hovered {
|
} else if response.hovered() {
|
||||||
&self.hovered
|
&self.hovered
|
||||||
} else {
|
} else {
|
||||||
&self.inactive
|
&self.inactive
|
||||||
|
|
|
@ -363,15 +363,29 @@ impl Ui {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rect_contains_mouse(&self, rect: Rect) -> bool {
|
/// Is the pointer (mouse/touch) above this rectangle in this `Ui`?
|
||||||
|
///
|
||||||
|
/// The `clip_rect` and layer of this `Ui` will be respected, so, for instance,
|
||||||
|
/// if this `Ui` is behind some other window, this will always return `false`.
|
||||||
|
pub fn rect_contains_pointer(&self, rect: Rect) -> bool {
|
||||||
self.ctx()
|
self.ctx()
|
||||||
.rect_contains_mouse(self.layer_id(), self.clip_rect().intersect(rect))
|
.rect_contains_pointer(self.layer_id(), self.clip_rect().intersect(rect))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Is the mouse above this `Ui`?
|
/// Is the pointer (mouse/touch) above this `Ui`?
|
||||||
/// Equivalent to `ui.rect_contains_mouse(ui.min_rect())`
|
/// Equivalent to `ui.rect_contains_pointer(ui.min_rect())`
|
||||||
|
pub fn ui_contains_pointer(&self) -> bool {
|
||||||
|
self.rect_contains_pointer(self.min_rect())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deprecated = "renamed rect_contains_pointer"]
|
||||||
|
pub fn rect_contains_mouse(&self, rect: Rect) -> bool {
|
||||||
|
self.rect_contains_pointer(rect)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deprecated = "renamed ui_contains_pointer"]
|
||||||
pub fn ui_contains_mouse(&self) -> bool {
|
pub fn ui_contains_mouse(&self) -> bool {
|
||||||
self.rect_contains_mouse(self.min_rect())
|
self.ui_contains_pointer()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deprecated = "Use: interact(rect, id, Sense::hover())"]
|
#[deprecated = "Use: interact(rect, id, Sense::hover())"]
|
||||||
|
@ -379,7 +393,7 @@ impl Ui {
|
||||||
self.interact(rect, self.auto_id_with("hover_rect"), Sense::hover())
|
self.interact(rect, self.auto_id_with("hover_rect"), Sense::hover())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deprecated = "Use: rect_contains_mouse()"]
|
#[deprecated = "Use: rect_contains_pointer()"]
|
||||||
pub fn hovered(&self, rect: Rect) -> bool {
|
pub fn hovered(&self, rect: Rect) -> bool {
|
||||||
self.interact(rect, self.id, Sense::hover()).hovered
|
self.interact(rect, self.id, Sense::hover()).hovered
|
||||||
}
|
}
|
||||||
|
@ -410,7 +424,7 @@ impl Ui {
|
||||||
/// ```
|
/// ```
|
||||||
/// # let mut ui = egui::Ui::__test();
|
/// # let mut ui = egui::Ui::__test();
|
||||||
/// let response = ui.allocate_response(egui::vec2(100.0, 200.0), egui::Sense::click());
|
/// let response = ui.allocate_response(egui::vec2(100.0, 200.0), egui::Sense::click());
|
||||||
/// if response.clicked { /* … */ }
|
/// if response.clicked() { /* … */ }
|
||||||
/// ui.painter().rect_stroke(response.rect, 0.0, (1.0, egui::Color32::WHITE));
|
/// ui.painter().rect_stroke(response.rect, 0.0, (1.0, egui::Color32::WHITE));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn allocate_response(&mut self, desired_size: Vec2, sense: Sense) -> Response {
|
pub fn allocate_response(&mut self, desired_size: Vec2, sense: Sense) -> Response {
|
||||||
|
@ -572,7 +586,7 @@ impl Ui {
|
||||||
/// # use egui::Align;
|
/// # use egui::Align;
|
||||||
/// # let mut ui = &mut egui::Ui::__test();
|
/// # let mut ui = &mut egui::Ui::__test();
|
||||||
/// egui::ScrollArea::auto_sized().show(ui, |ui| {
|
/// egui::ScrollArea::auto_sized().show(ui, |ui| {
|
||||||
/// let scroll_bottom = ui.button("Scroll to bottom.").clicked;
|
/// let scroll_bottom = ui.button("Scroll to bottom.").clicked();
|
||||||
/// for i in 0..1000 {
|
/// for i in 0..1000 {
|
||||||
/// ui.label(format!("Item {}", i));
|
/// ui.label(format!("Item {}", i));
|
||||||
/// }
|
/// }
|
||||||
|
@ -654,20 +668,20 @@ impl Ui {
|
||||||
self.add(TextEdit::multiline(text))
|
self.add(TextEdit::multiline(text))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Usage: `if ui.button("Click me").clicked { ... }`
|
/// Usage: `if ui.button("Click me").clicked() { ... }`
|
||||||
///
|
///
|
||||||
/// Shortcut for `add(Button::new(text))`
|
/// Shortcut for `add(Button::new(text))`
|
||||||
#[must_use = "You should check if the user clicked this with `if ui.button(...).clicked { ... } "]
|
#[must_use = "You should check if the user clicked this with `if ui.button(...).clicked() { ... } "]
|
||||||
pub fn button(&mut self, text: impl Into<String>) -> Response {
|
pub fn button(&mut self, text: impl Into<String>) -> Response {
|
||||||
self.add(Button::new(text))
|
self.add(Button::new(text))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A button as small as normal body text.
|
/// A button as small as normal body text.
|
||||||
///
|
///
|
||||||
/// Usage: `if ui.small_button("Click me").clicked { ... }`
|
/// Usage: `if ui.small_button("Click me").clicked() { ... }`
|
||||||
///
|
///
|
||||||
/// Shortcut for `add(Button::new(text).small())`
|
/// Shortcut for `add(Button::new(text).small())`
|
||||||
#[must_use = "You should check if the user clicked this with `if ui.small_button(...).clicked { ... } "]
|
#[must_use = "You should check if the user clicked this with `if ui.small_button(...).clicked() { ... } "]
|
||||||
pub fn small_button(&mut self, text: impl Into<String>) -> Response {
|
pub fn small_button(&mut self, text: impl Into<String>) -> Response {
|
||||||
self.add(Button::new(text).small())
|
self.add(Button::new(text).small())
|
||||||
}
|
}
|
||||||
|
@ -694,7 +708,7 @@ impl Ui {
|
||||||
text: impl Into<String>,
|
text: impl Into<String>,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
let response = self.radio(*current_value == selected_value, text);
|
let response = self.radio(*current_value == selected_value, text);
|
||||||
if response.clicked {
|
if response.clicked() {
|
||||||
*current_value = selected_value;
|
*current_value = selected_value;
|
||||||
}
|
}
|
||||||
response
|
response
|
||||||
|
@ -716,7 +730,7 @@ impl Ui {
|
||||||
text: impl Into<String>,
|
text: impl Into<String>,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
let response = self.selectable_label(*current_value == selected_value, text);
|
let response = self.selectable_label(*current_value == selected_value, text);
|
||||||
if response.clicked {
|
if response.clicked() {
|
||||||
*current_value = selected_value;
|
*current_value = selected_value;
|
||||||
}
|
}
|
||||||
response
|
response
|
||||||
|
|
|
@ -191,7 +191,7 @@ impl<'a> Widget for Checkbox<'a> {
|
||||||
desired_size = desired_size.at_least(spacing.interact_size);
|
desired_size = desired_size.at_least(spacing.interact_size);
|
||||||
desired_size.y = desired_size.y.max(icon_width);
|
desired_size.y = desired_size.y.max(icon_width);
|
||||||
let (rect, response) = ui.allocate_exact_size(desired_size, Sense::click());
|
let (rect, response) = ui.allocate_exact_size(desired_size, Sense::click());
|
||||||
if response.clicked {
|
if response.clicked() {
|
||||||
*checked = !*checked;
|
*checked = !*checked;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -94,10 +94,8 @@ fn color_slider_1d(ui: &mut Ui, value: &mut f32, color_at: impl Fn(f32) -> Color
|
||||||
);
|
);
|
||||||
let (rect, response) = ui.allocate_at_least(desired_size, Sense::click_and_drag());
|
let (rect, response) = ui.allocate_at_least(desired_size, Sense::click_and_drag());
|
||||||
|
|
||||||
if response.active {
|
if let Some(mpos) = response.interact_pointer_pos() {
|
||||||
if let Some(mpos) = ui.input().mouse.pos {
|
*value = remap_clamp(mpos.x, rect.left()..=rect.right(), 0.0..=1.0);
|
||||||
*value = remap_clamp(mpos.x, rect.left()..=rect.right(), 0.0..=1.0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let visuals = ui.style().interact(&response);
|
let visuals = ui.style().interact(&response);
|
||||||
|
@ -151,11 +149,9 @@ fn color_slider_2d(
|
||||||
let desired_size = Vec2::splat(ui.style().spacing.slider_width);
|
let desired_size = Vec2::splat(ui.style().spacing.slider_width);
|
||||||
let (rect, response) = ui.allocate_at_least(desired_size, Sense::click_and_drag());
|
let (rect, response) = ui.allocate_at_least(desired_size, Sense::click_and_drag());
|
||||||
|
|
||||||
if response.active {
|
if let Some(mpos) = response.interact_pointer_pos() {
|
||||||
if let Some(mpos) = ui.input().mouse.pos {
|
*x_value = remap_clamp(mpos.x, rect.left()..=rect.right(), 0.0..=1.0);
|
||||||
*x_value = remap_clamp(mpos.x, rect.left()..=rect.right(), 0.0..=1.0);
|
*y_value = remap_clamp(mpos.y, rect.bottom()..=rect.top(), 0.0..=1.0);
|
||||||
*y_value = remap_clamp(mpos.y, rect.bottom()..=rect.top(), 0.0..=1.0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let visuals = ui.style().interact(&response);
|
let visuals = ui.style().interact(&response);
|
||||||
|
@ -217,7 +213,7 @@ fn color_text_ui(ui: &mut Ui, color: impl Into<Color32>) {
|
||||||
r, g, b, a
|
r, g, b, a
|
||||||
));
|
));
|
||||||
|
|
||||||
if ui.button("📋").on_hover_text("Click to copy").clicked {
|
if ui.button("📋").on_hover_text("Click to copy").clicked() {
|
||||||
ui.output().copied_text = format!("rgba({}, {}, {}, {})", r, g, b, a);
|
ui.output().copied_text = format!("rgba({}, {}, {}, {})", r, g, b, a);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -323,7 +319,7 @@ pub fn color_edit_button_hsva(ui: &mut Ui, hsva: &mut Hsva, alpha: Alpha) -> Res
|
||||||
let pupup_id = ui.auto_id_with("popup");
|
let pupup_id = ui.auto_id_with("popup");
|
||||||
let button_response = color_button(ui, (*hsva).into()).on_hover_text("Click to edit color");
|
let button_response = color_button(ui, (*hsva).into()).on_hover_text("Click to edit color");
|
||||||
|
|
||||||
if button_response.clicked {
|
if button_response.clicked() {
|
||||||
ui.memory().toggle_popup(pupup_id);
|
ui.memory().toggle_popup(pupup_id);
|
||||||
}
|
}
|
||||||
// TODO: make it easier to show a temporary popup that closes when you click outside it
|
// TODO: make it easier to show a temporary popup that closes when you click outside it
|
||||||
|
@ -338,8 +334,8 @@ pub fn color_edit_button_hsva(ui: &mut Ui, hsva: &mut Hsva, alpha: Alpha) -> Res
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
if !button_response.clicked {
|
if !button_response.clicked() {
|
||||||
let clicked_outside = ui.input().mouse.click && !area_response.hovered;
|
let clicked_outside = ui.input().pointer.any_pressed() && !area_response.hovered();
|
||||||
if clicked_outside || ui.input().key_pressed(Key::Escape) {
|
if clicked_outside || ui.input().key_pressed(Key::Escape) {
|
||||||
ui.memory().close_popup();
|
ui.memory().close_popup();
|
||||||
}
|
}
|
||||||
|
|
|
@ -185,11 +185,11 @@ impl<'a> Widget for DragValue<'a> {
|
||||||
value as f32, // Show full precision value on-hover. TODO: figure out f64 vs f32
|
value as f32, // Show full precision value on-hover. TODO: figure out f64 vs f32
|
||||||
suffix
|
suffix
|
||||||
));
|
));
|
||||||
if response.clicked {
|
if response.clicked() {
|
||||||
ui.memory().request_kb_focus(kb_edit_id);
|
ui.memory().request_kb_focus(kb_edit_id);
|
||||||
ui.memory().temp_edit_string = None; // Filled in next frame
|
ui.memory().temp_edit_string = None; // Filled in next frame
|
||||||
} else if response.active {
|
} else if response.dragged() {
|
||||||
let mdelta = ui.input().mouse.delta;
|
let mdelta = ui.input().pointer.delta();
|
||||||
let delta_points = mdelta.x - mdelta.y; // Increase to the right and up
|
let delta_points = mdelta.x - mdelta.y; // Increase to the right and up
|
||||||
let delta_value = speed * delta_points;
|
let delta_value = speed * delta_points;
|
||||||
if delta_value != 0.0 {
|
if delta_value != 0.0 {
|
||||||
|
|
|
@ -48,17 +48,17 @@ impl Widget for Hyperlink {
|
||||||
let galley = font.layout_multiline(text, ui.available_width());
|
let galley = font.layout_multiline(text, ui.available_width());
|
||||||
let (rect, response) = ui.allocate_exact_size(galley.size, Sense::click());
|
let (rect, response) = ui.allocate_exact_size(galley.size, Sense::click());
|
||||||
|
|
||||||
if response.hovered {
|
if response.hovered() {
|
||||||
ui.ctx().output().cursor_icon = CursorIcon::PointingHand;
|
ui.ctx().output().cursor_icon = CursorIcon::PointingHand;
|
||||||
}
|
}
|
||||||
if response.clicked {
|
if response.clicked() {
|
||||||
ui.ctx().output().open_url = Some(url.clone());
|
ui.ctx().output().open_url = Some(url.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
let color = ui.style().visuals.hyperlink_color;
|
let color = ui.style().visuals.hyperlink_color;
|
||||||
let visuals = ui.style().interact(&response);
|
let visuals = ui.style().interact(&response);
|
||||||
|
|
||||||
if response.hovered {
|
if response.hovered() {
|
||||||
// Underline:
|
// Underline:
|
||||||
for line in &galley.rows {
|
for line in &galley.rows {
|
||||||
let pos = rect.min;
|
let pos = rect.min;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
//!
|
//!
|
||||||
//! Example widget uses:
|
//! Example widget uses:
|
||||||
//! * `ui.add(Label::new("Text").text_color(color::red));`
|
//! * `ui.add(Label::new("Text").text_color(color::red));`
|
||||||
//! * `if ui.add(Button::new("Click me")).clicked { ... }`
|
//! * `if ui.add(Button::new("Click me")).clicked() { ... }`
|
||||||
|
|
||||||
#![allow(clippy::new_without_default)]
|
#![allow(clippy::new_without_default)]
|
||||||
|
|
||||||
|
@ -40,7 +40,10 @@ pub trait Widget {
|
||||||
/// The button is only enabled if the value does not already have its original value.
|
/// The button is only enabled if the value does not already have its original value.
|
||||||
pub fn reset_button<T: Default + PartialEq>(ui: &mut Ui, value: &mut T) {
|
pub fn reset_button<T: Default + PartialEq>(ui: &mut Ui, value: &mut T) {
|
||||||
let def = T::default();
|
let def = T::default();
|
||||||
if ui.add(Button::new("Reset").enabled(*value != def)).clicked {
|
if ui
|
||||||
|
.add(Button::new("Reset").enabled(*value != def))
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
*value = def;
|
*value = def;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ impl Widget for SelectableLabel {
|
||||||
|
|
||||||
let visuals = ui.style().interact(&response);
|
let visuals = ui.style().interact(&response);
|
||||||
|
|
||||||
if selected || response.hovered {
|
if selected || response.hovered() {
|
||||||
let rect = rect.expand(visuals.expansion);
|
let rect = rect.expand(visuals.expansion);
|
||||||
let fill = if selected {
|
let fill = if selected {
|
||||||
ui.style().visuals.selection.bg_fill
|
ui.style().visuals.selection.bg_fill
|
||||||
|
|
|
@ -252,19 +252,17 @@ impl<'a> Slider<'a> {
|
||||||
let rect = &response.rect;
|
let rect = &response.rect;
|
||||||
let x_range = x_range(rect);
|
let x_range = x_range(rect);
|
||||||
|
|
||||||
if let Some(mouse_pos) = ui.input().mouse.pos {
|
if let Some(pointer_pos) = response.interact_pointer_pos() {
|
||||||
if response.active {
|
let new_value = if self.smart_aim {
|
||||||
let new_value = if self.smart_aim {
|
let aim_radius = ui.input().aim_radius();
|
||||||
let aim_radius = ui.input().aim_radius();
|
crate::math::smart_aim::best_in_range_f64(
|
||||||
crate::math::smart_aim::best_in_range_f64(
|
self.value_from_x(pointer_pos.x - aim_radius, x_range.clone()),
|
||||||
self.value_from_x(mouse_pos.x - aim_radius, x_range.clone()),
|
self.value_from_x(pointer_pos.x + aim_radius, x_range.clone()),
|
||||||
self.value_from_x(mouse_pos.x + aim_radius, x_range.clone()),
|
)
|
||||||
)
|
} else {
|
||||||
} else {
|
self.value_from_x(pointer_pos.x, x_range.clone())
|
||||||
self.value_from_x(mouse_pos.x, x_range.clone())
|
};
|
||||||
};
|
self.set_value(new_value);
|
||||||
self.set_value(new_value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Paint it:
|
// Paint it:
|
||||||
|
@ -350,7 +348,7 @@ impl<'a> Slider<'a> {
|
||||||
self.get_value() as f32 // Show full precision value on-hover. TODO: figure out f64 vs f32
|
self.get_value() as f32 // Show full precision value on-hover. TODO: figure out f64 vs f32
|
||||||
));
|
));
|
||||||
// let response = ui.interact(response.rect, kb_edit_id, Sense::click());
|
// let response = ui.interact(response.rect, kb_edit_id, Sense::click());
|
||||||
if response.clicked {
|
if response.clicked() {
|
||||||
ui.memory().request_kb_focus(kb_edit_id);
|
ui.memory().request_kb_focus(kb_edit_id);
|
||||||
ui.memory().temp_edit_string = None; // Filled in next frame
|
ui.memory().temp_edit_string = None; // Filled in next frame
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,7 +115,7 @@ impl CCursorPair {
|
||||||
/// # let mut ui = egui::Ui::__test();
|
/// # let mut ui = egui::Ui::__test();
|
||||||
/// # let mut my_string = String::new();
|
/// # let mut my_string = String::new();
|
||||||
/// let response = ui.add(egui::TextEdit::singleline(&mut my_string));
|
/// let response = ui.add(egui::TextEdit::singleline(&mut my_string));
|
||||||
/// if response.lost_kb_focus {
|
/// if response.lost_kb_focus() {
|
||||||
/// // use my_string
|
/// // use my_string
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -318,45 +318,45 @@ impl<'t> TextEdit<'t> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if enabled {
|
if enabled {
|
||||||
if let Some(mouse_pos) = ui.input().mouse.pos {
|
if let Some(pointer_pos) = ui.input().pointer.interact_pos() {
|
||||||
// TODO: triple-click to select whole paragraph
|
// TODO: triple-click to select whole paragraph
|
||||||
// TODO: drag selected text to either move or clone (ctrl on windows, alt on mac)
|
// TODO: drag selected text to either move or clone (ctrl on windows, alt on mac)
|
||||||
|
|
||||||
let cursor_at_mouse = galley.cursor_from_pos(mouse_pos - response.rect.min);
|
let cursor_at_pointer = galley.cursor_from_pos(pointer_pos - response.rect.min);
|
||||||
|
|
||||||
if response.hovered && ui.input().mouse.is_moving() {
|
if response.hovered() && ui.input().pointer.is_moving() {
|
||||||
// preview:
|
// preview:
|
||||||
paint_cursor_end(ui, response.rect.min, &galley, &cursor_at_mouse);
|
paint_cursor_end(ui, response.rect.min, &galley, &cursor_at_pointer);
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.hovered && response.double_clicked {
|
if response.hovered() && response.double_clicked() {
|
||||||
// Select word:
|
// Select word:
|
||||||
let center = cursor_at_mouse;
|
let center = cursor_at_pointer;
|
||||||
let ccursorp = select_word_at(text, center.ccursor);
|
let ccursorp = select_word_at(text, center.ccursor);
|
||||||
state.cursorp = Some(CursorPair {
|
state.cursorp = Some(CursorPair {
|
||||||
primary: galley.from_ccursor(ccursorp.primary),
|
primary: galley.from_ccursor(ccursorp.primary),
|
||||||
secondary: galley.from_ccursor(ccursorp.secondary),
|
secondary: galley.from_ccursor(ccursorp.secondary),
|
||||||
});
|
});
|
||||||
} else if response.hovered && ui.input().mouse.pressed {
|
} else if response.hovered() && ui.input().pointer.any_pressed() {
|
||||||
ui.memory().request_kb_focus(id);
|
ui.memory().request_kb_focus(id);
|
||||||
if ui.input().modifiers.shift {
|
if ui.input().modifiers.shift {
|
||||||
if let Some(cursorp) = &mut state.cursorp {
|
if let Some(cursorp) = &mut state.cursorp {
|
||||||
cursorp.primary = cursor_at_mouse;
|
cursorp.primary = cursor_at_pointer;
|
||||||
} else {
|
} else {
|
||||||
state.cursorp = Some(CursorPair::one(cursor_at_mouse));
|
state.cursorp = Some(CursorPair::one(cursor_at_pointer));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
state.cursorp = Some(CursorPair::one(cursor_at_mouse));
|
state.cursorp = Some(CursorPair::one(cursor_at_pointer));
|
||||||
}
|
}
|
||||||
} else if ui.input().mouse.down && response.active {
|
} else if ui.input().pointer.any_down() && response.is_pointer_button_down_on() {
|
||||||
if let Some(cursorp) = &mut state.cursorp {
|
if let Some(cursorp) = &mut state.cursorp {
|
||||||
cursorp.primary = cursor_at_mouse;
|
cursorp.primary = cursor_at_pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ui.input().mouse.pressed && !response.hovered {
|
if ui.input().pointer.any_pressed() && !response.hovered() {
|
||||||
// User clicked somewhere else
|
// User clicked somewhere else
|
||||||
ui.memory().surrender_kb_focus(id);
|
ui.memory().surrender_kb_focus(id);
|
||||||
}
|
}
|
||||||
|
@ -365,7 +365,7 @@ impl<'t> TextEdit<'t> {
|
||||||
ui.memory().surrender_kb_focus(id);
|
ui.memory().surrender_kb_focus(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.hovered && enabled {
|
if response.hovered() && enabled {
|
||||||
ui.output().cursor_icon = CursorIcon::Text;
|
ui.output().cursor_icon = CursorIcon::Text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -471,7 +471,7 @@ impl<'t> TextEdit<'t> {
|
||||||
modifiers,
|
modifiers,
|
||||||
} => on_key_press(&mut cursorp, text, &galley, *key, modifiers),
|
} => on_key_press(&mut cursorp, text, &galley, *key, modifiers),
|
||||||
|
|
||||||
Event::Key { .. } => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(new_ccursorp) = did_mutate_text {
|
if let Some(new_ccursorp) = did_mutate_text {
|
||||||
|
|
|
@ -57,7 +57,7 @@ impl DemoWindow {
|
||||||
ui.columns(self.num_columns, |cols| {
|
ui.columns(self.num_columns, |cols| {
|
||||||
for (i, col) in cols.iter_mut().enumerate() {
|
for (i, col) in cols.iter_mut().enumerate() {
|
||||||
col.label(format!("Column {} out of {}", i + 1, self.num_columns));
|
col.label(format!("Column {} out of {}", i + 1, self.num_columns));
|
||||||
if i + 1 == self.num_columns && col.button("Delete this").clicked {
|
if i + 1 == self.num_columns && col.button("Delete this").clicked() {
|
||||||
self.num_columns -= 1;
|
self.num_columns -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -287,15 +287,15 @@ impl LayoutDemo {
|
||||||
|
|
||||||
pub fn content_ui(&mut self, ui: &mut Ui) {
|
pub fn content_ui(&mut self, ui: &mut Ui) {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
if ui.button("Top-down").clicked {
|
if ui.button("Top-down").clicked() {
|
||||||
*self = Default::default();
|
*self = Default::default();
|
||||||
}
|
}
|
||||||
if ui.button("Top-down, centered and justified").clicked {
|
if ui.button("Top-down, centered and justified").clicked() {
|
||||||
*self = Default::default();
|
*self = Default::default();
|
||||||
self.cross_align = Align::Center;
|
self.cross_align = Align::Center;
|
||||||
self.cross_justify = true;
|
self.cross_justify = true;
|
||||||
}
|
}
|
||||||
if ui.button("Horizontal wrapped").clicked {
|
if ui.button("Horizontal wrapped").clicked() {
|
||||||
*self = Default::default();
|
*self = Default::default();
|
||||||
self.main_dir = Direction::LeftToRight;
|
self.main_dir = Direction::LeftToRight;
|
||||||
self.cross_align = Align::Center;
|
self.cross_align = Align::Center;
|
||||||
|
@ -391,7 +391,7 @@ impl Tree {
|
||||||
if depth > 0
|
if depth > 0
|
||||||
&& ui
|
&& ui
|
||||||
.add(Button::new("delete").text_color(Color32::RED))
|
.add(Button::new("delete").text_color(Color32::RED))
|
||||||
.clicked
|
.clicked()
|
||||||
{
|
{
|
||||||
return Action::Delete;
|
return Action::Delete;
|
||||||
}
|
}
|
||||||
|
@ -409,7 +409,7 @@ impl Tree {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if ui.button("+").clicked {
|
if ui.button("+").clicked() {
|
||||||
self.0.push(Tree::default());
|
self.0.push(Tree::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ impl Default for Demos {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let demos: Vec<Box<dyn super::Demo>> = vec![
|
let demos: Vec<Box<dyn super::Demo>> = vec![
|
||||||
Box::new(super::WidgetGallery::default()),
|
Box::new(super::WidgetGallery::default()),
|
||||||
|
Box::new(super::input_test::InputTest::default()),
|
||||||
Box::new(super::FontBook::default()),
|
Box::new(super::FontBook::default()),
|
||||||
Box::new(super::Painting::default()),
|
Box::new(super::Painting::default()),
|
||||||
Box::new(super::DancingStrings::default()),
|
Box::new(super::DancingStrings::default()),
|
||||||
|
@ -86,7 +87,7 @@ impl DemoWindows {
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
if ui.button("Organize windows").clicked {
|
if ui.button("Organize windows").clicked() {
|
||||||
ui.ctx().memory().reset_areas();
|
ui.ctx().memory().reset_areas();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -255,13 +256,13 @@ fn show_menu_bar(ui: &mut Ui) {
|
||||||
|
|
||||||
menu::bar(ui, |ui| {
|
menu::bar(ui, |ui| {
|
||||||
menu::menu(ui, "File", |ui| {
|
menu::menu(ui, "File", |ui| {
|
||||||
if ui.button("Organize windows").clicked {
|
if ui.button("Organize windows").clicked() {
|
||||||
ui.ctx().memory().reset_areas();
|
ui.ctx().memory().reset_areas();
|
||||||
}
|
}
|
||||||
if ui
|
if ui
|
||||||
.button("Clear egui memory")
|
.button("Clear egui memory")
|
||||||
.on_hover_text("Forget scroll, collapsing headers etc")
|
.on_hover_text("Forget scroll, collapsing headers etc")
|
||||||
.clicked
|
.clicked()
|
||||||
{
|
{
|
||||||
*ui.ctx().memory() = Default::default();
|
*ui.ctx().memory() = Default::default();
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ pub fn drag_source(ui: &mut Ui, id: Id, body: impl FnOnce(&mut Ui)) {
|
||||||
|
|
||||||
// Check for drags:
|
// Check for drags:
|
||||||
let response = ui.interact(response.rect, id, Sense::drag());
|
let response = ui.interact(response.rect, id, Sense::drag());
|
||||||
if response.hovered {
|
if response.hovered() {
|
||||||
ui.output().cursor_icon = CursorIcon::Grab;
|
ui.output().cursor_icon = CursorIcon::Grab;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -25,8 +25,8 @@ pub fn drag_source(ui: &mut Ui, id: Id, body: impl FnOnce(&mut Ui)) {
|
||||||
// (anything with `Order::Tooltip` always gets an empty `Response`)
|
// (anything with `Order::Tooltip` always gets an empty `Response`)
|
||||||
// So this is fine!
|
// So this is fine!
|
||||||
|
|
||||||
if let Some(mouse_pos) = ui.input().mouse.pos {
|
if let Some(pointer_pos) = ui.input().pointer.interact_pos() {
|
||||||
let delta = mouse_pos - response.rect.center();
|
let delta = pointer_pos - response.rect.center();
|
||||||
ui.ctx().translate_layer(layer_id, delta);
|
ui.ctx().translate_layer(layer_id, delta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ pub fn drop_target<R>(
|
||||||
let outer_rect = Rect::from_min_max(outer_rect_bounds.min, content_ui.min_rect().max + margin);
|
let outer_rect = Rect::from_min_max(outer_rect_bounds.min, content_ui.min_rect().max + margin);
|
||||||
let (rect, response) = ui.allocate_at_least(outer_rect.size(), Sense::hover());
|
let (rect, response) = ui.allocate_at_least(outer_rect.size(), Sense::hover());
|
||||||
|
|
||||||
let style = if is_being_dragged && can_accept_what_is_being_dragged && response.hovered {
|
let style = if is_being_dragged && can_accept_what_is_being_dragged && response.hovered() {
|
||||||
ui.style().visuals.widgets.active
|
ui.style().visuals.widgets.active
|
||||||
} else if is_being_dragged && can_accept_what_is_being_dragged {
|
} else if is_being_dragged && can_accept_what_is_being_dragged {
|
||||||
ui.style().visuals.widgets.inactive
|
ui.style().visuals.widgets.inactive
|
||||||
|
@ -126,8 +126,7 @@ impl super::View for DragAndDropDemo {
|
||||||
ui.label(item);
|
ui.label(item);
|
||||||
});
|
});
|
||||||
|
|
||||||
let this_item_being_dragged = ui.memory().is_being_dragged(item_id);
|
if ui.memory().is_being_dragged(item_id) {
|
||||||
if this_item_being_dragged {
|
|
||||||
source_col_row = Some((col_idx, row_idx));
|
source_col_row = Some((col_idx, row_idx));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -135,7 +134,7 @@ impl super::View for DragAndDropDemo {
|
||||||
.1;
|
.1;
|
||||||
|
|
||||||
let is_being_dragged = ui.memory().is_anything_being_dragged();
|
let is_being_dragged = ui.memory().is_anything_being_dragged();
|
||||||
if is_being_dragged && can_accept_what_is_being_dragged && response.hovered {
|
if is_being_dragged && can_accept_what_is_being_dragged && response.hovered() {
|
||||||
drop_col = Some(col_idx);
|
drop_col = Some(col_idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,7 +142,7 @@ impl super::View for DragAndDropDemo {
|
||||||
|
|
||||||
if let Some((source_col, source_row)) = source_col_row {
|
if let Some((source_col, source_row)) = source_col_row {
|
||||||
if let Some(drop_col) = drop_col {
|
if let Some(drop_col) = drop_col {
|
||||||
if ui.input().mouse.released {
|
if ui.input().pointer.any_released() {
|
||||||
// do the drop:
|
// do the drop:
|
||||||
let item = self.columns[source_col].remove(source_row);
|
let item = self.columns[source_col].remove(source_row);
|
||||||
self.columns[drop_col].push(item);
|
self.columns[drop_col].push(item);
|
||||||
|
|
|
@ -31,7 +31,7 @@ impl FontBook {
|
||||||
ui.label(format!("{}\nU+{:X}\n\nClick to copy", name, chr as u32));
|
ui.label(format!("{}\nU+{:X}\n\nClick to copy", name, chr as u32));
|
||||||
};
|
};
|
||||||
|
|
||||||
if ui.add(button).on_hover_ui(tooltip_ui).clicked {
|
if ui.add(button).on_hover_ui(tooltip_ui).clicked() {
|
||||||
ui.output().copied_text = chr.to_string();
|
ui.output().copied_text = chr.to_string();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ impl super::View for FontBook {
|
||||||
ui.label("Filter:");
|
ui.label("Filter:");
|
||||||
ui.text_edit_singleline(&mut self.filter);
|
ui.text_edit_singleline(&mut self.filter);
|
||||||
self.filter = self.filter.to_lowercase();
|
self.filter = self.filter.to_lowercase();
|
||||||
if ui.button("x").clicked {
|
if ui.button("x").clicked() {
|
||||||
self.filter.clear();
|
self.filter.clear();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
52
egui_demo_lib/src/apps/demo/input_test.rs
Normal file
52
egui_demo_lib/src/apps/demo/input_test.rs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct InputTest {
|
||||||
|
info: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl super::Demo for InputTest {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"🖱 Input Test"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
|
||||||
|
egui::Window::new(self.name())
|
||||||
|
.open(open)
|
||||||
|
.resizable(false)
|
||||||
|
.show(ctx, |ui| {
|
||||||
|
use super::View;
|
||||||
|
self.ui(ui);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl super::View for InputTest {
|
||||||
|
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||||
|
let response = ui.add(
|
||||||
|
egui::Button::new("Click, double-click or drag me with any mouse button")
|
||||||
|
.sense(egui::Sense::click_and_drag()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut new_info = String::new();
|
||||||
|
for &button in &[
|
||||||
|
egui::PointerButton::Primary,
|
||||||
|
egui::PointerButton::Secondary,
|
||||||
|
egui::PointerButton::Middle,
|
||||||
|
] {
|
||||||
|
if response.clicked_by(button) {
|
||||||
|
new_info += &format!("Clicked by {:?}\n", button);
|
||||||
|
}
|
||||||
|
if response.double_clicked_by(button) {
|
||||||
|
new_info += &format!("Double-clicked by {:?}\n", button);
|
||||||
|
}
|
||||||
|
if response.dragged() && ui.input().pointer.button_down(button) {
|
||||||
|
new_info += &format!("Dragged by {:?}\n", button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !new_info.is_empty() {
|
||||||
|
self.info = new_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.label(&self.info);
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ mod drag_and_drop;
|
||||||
mod font_book;
|
mod font_book;
|
||||||
pub mod font_contents_emoji;
|
pub mod font_contents_emoji;
|
||||||
pub mod font_contents_ubuntu;
|
pub mod font_contents_ubuntu;
|
||||||
|
pub mod input_test;
|
||||||
mod painting;
|
mod painting;
|
||||||
mod scrolls;
|
mod scrolls;
|
||||||
mod sliders;
|
mod sliders;
|
||||||
|
|
|
@ -21,7 +21,7 @@ impl Painting {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
egui::stroke_ui(ui, &mut self.stroke, "Stroke");
|
egui::stroke_ui(ui, &mut self.stroke, "Stroke");
|
||||||
ui.separator();
|
ui.separator();
|
||||||
if ui.button("Clear Painting").clicked {
|
if ui.button("Clear Painting").clicked() {
|
||||||
self.lines.clear();
|
self.lines.clear();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -38,12 +38,10 @@ impl Painting {
|
||||||
|
|
||||||
let current_line = self.lines.last_mut().unwrap();
|
let current_line = self.lines.last_mut().unwrap();
|
||||||
|
|
||||||
if response.active {
|
if let Some(pointer_pos) = response.interact_pointer_pos() {
|
||||||
if let Some(mouse_pos) = ui.input().mouse.pos {
|
let canvas_pos = pointer_pos - rect.min;
|
||||||
let canvas_pos = mouse_pos - rect.min;
|
if current_line.last() != Some(&canvas_pos) {
|
||||||
if current_line.last() != Some(&canvas_pos) {
|
current_line.push(canvas_pos);
|
||||||
current_line.push(canvas_pos);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if !current_line.is_empty() {
|
} else if !current_line.is_empty() {
|
||||||
self.lines.push(vec![]);
|
self.lines.push(vec![]);
|
||||||
|
|
|
@ -33,13 +33,13 @@ impl Scrolls {
|
||||||
ui.add(Slider::usize(&mut self.track_item, 1..=50).text("Track Item"));
|
ui.add(Slider::usize(&mut self.track_item, 1..=50).text("Track Item"));
|
||||||
});
|
});
|
||||||
let (scroll_offset, _) = ui.horizontal(|ui| {
|
let (scroll_offset, _) = ui.horizontal(|ui| {
|
||||||
let scroll_offset = ui.small_button("Scroll Offset").clicked;
|
let scroll_offset = ui.small_button("Scroll Offset").clicked();
|
||||||
ui.add(DragValue::f32(&mut self.offset).speed(1.0).suffix("px"));
|
ui.add(DragValue::f32(&mut self.offset).speed(1.0).suffix("px"));
|
||||||
scroll_offset
|
scroll_offset
|
||||||
});
|
});
|
||||||
|
|
||||||
let scroll_top = ui.button("Scroll to top").clicked;
|
let scroll_top = ui.button("Scroll to top").clicked();
|
||||||
let scroll_bottom = ui.button("Scroll to bottom").clicked;
|
let scroll_bottom = ui.button("Scroll to bottom").clicked();
|
||||||
if scroll_bottom || scroll_top {
|
if scroll_bottom || scroll_top {
|
||||||
self.tracking = false;
|
self.tracking = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,7 @@ impl Sliders {
|
||||||
You can always see the full precision value by hovering the value.",
|
You can always see the full precision value by hovering the value.",
|
||||||
);
|
);
|
||||||
|
|
||||||
if ui.button("Assign PI").clicked {
|
if ui.button("Assign PI").clicked() {
|
||||||
self.value = std::f64::consts::PI;
|
self.value = std::f64::consts::PI;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ pub fn toggle(ui: &mut egui::Ui, on: &mut bool) -> egui::Response {
|
||||||
let (rect, response) = ui.allocate_exact_size(desired_size, egui::Sense::click());
|
let (rect, response) = ui.allocate_exact_size(desired_size, egui::Sense::click());
|
||||||
|
|
||||||
// 3. Interact: Time to check for clicks!.
|
// 3. Interact: Time to check for clicks!.
|
||||||
if response.clicked {
|
if response.clicked() {
|
||||||
*on = !*on;
|
*on = !*on;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ pub fn toggle(ui: &mut egui::Ui, on: &mut bool) -> egui::Response {
|
||||||
fn toggle_compact(ui: &mut egui::Ui, on: &mut bool) -> egui::Response {
|
fn toggle_compact(ui: &mut egui::Ui, on: &mut bool) -> egui::Response {
|
||||||
let desired_size = ui.style().spacing.interact_size;
|
let desired_size = ui.style().spacing.interact_size;
|
||||||
let (rect, response) = ui.allocate_exact_size(desired_size, egui::Sense::click());
|
let (rect, response) = ui.allocate_exact_size(desired_size, egui::Sense::click());
|
||||||
*on ^= response.clicked; // toggle if clicked
|
*on ^= response.clicked(); // toggle if clicked
|
||||||
|
|
||||||
let how_on = ui.ctx().animate_bool(response.id, *on);
|
let how_on = ui.ctx().animate_bool(response.id, *on);
|
||||||
let visuals = ui.style().interact(&response);
|
let visuals = ui.style().interact(&response);
|
||||||
|
|
|
@ -115,7 +115,7 @@ impl super::View for WidgetGallery {
|
||||||
ui.end_row();
|
ui.end_row();
|
||||||
|
|
||||||
ui.label("Button:");
|
ui.label("Button:");
|
||||||
if ui.button("Toggle boolean").clicked {
|
if ui.button("Toggle boolean").clicked() {
|
||||||
*boolean = !*boolean;
|
*boolean = !*boolean;
|
||||||
}
|
}
|
||||||
ui.end_row();
|
ui.end_row();
|
||||||
|
@ -123,7 +123,7 @@ impl super::View for WidgetGallery {
|
||||||
ui.label("ImageButton:");
|
ui.label("ImageButton:");
|
||||||
if ui
|
if ui
|
||||||
.add(egui::ImageButton::new(egui::TextureId::Egui, [24.0, 16.0]))
|
.add(egui::ImageButton::new(egui::TextureId::Egui, [24.0, 16.0]))
|
||||||
.clicked
|
.clicked()
|
||||||
{
|
{
|
||||||
*boolean = !*boolean;
|
*boolean = !*boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,7 +97,7 @@ impl Widgets {
|
||||||
if ui
|
if ui
|
||||||
.add(Button::new("Click me").enabled(self.button_enabled))
|
.add(Button::new("Click me").enabled(self.button_enabled))
|
||||||
.on_hover_text("This will just increase a counter.")
|
.on_hover_text("This will just increase a counter.")
|
||||||
.clicked
|
.clicked()
|
||||||
{
|
{
|
||||||
self.count += 1;
|
self.count += 1;
|
||||||
}
|
}
|
||||||
|
@ -146,7 +146,7 @@ impl Widgets {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Single line text input:");
|
ui.label("Single line text input:");
|
||||||
let response = ui.text_edit_singleline(&mut self.single_line_text_input);
|
let response = ui.text_edit_singleline(&mut self.single_line_text_input);
|
||||||
if response.lost_kb_focus {
|
if response.lost_kb_focus() {
|
||||||
// The user pressed enter.
|
// The user pressed enter.
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -113,8 +113,8 @@ fn ui_url(ui: &mut egui::Ui, frame: &mut epi::Frame<'_>, url: &mut String) -> Op
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("URL:");
|
ui.label("URL:");
|
||||||
trigger_fetch |= ui.text_edit_singleline(url).lost_kb_focus;
|
trigger_fetch |= ui.text_edit_singleline(url).lost_kb_focus();
|
||||||
trigger_fetch |= ui.button("GET").clicked;
|
trigger_fetch |= ui.button("GET").clicked();
|
||||||
});
|
});
|
||||||
|
|
||||||
if frame.is_web() {
|
if frame.is_web() {
|
||||||
|
@ -122,14 +122,14 @@ fn ui_url(ui: &mut egui::Ui, frame: &mut epi::Frame<'_>, url: &mut String) -> Op
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
if ui.button("Source code for this example").clicked {
|
if ui.button("Source code for this example").clicked() {
|
||||||
*url = format!(
|
*url = format!(
|
||||||
"https://raw.githubusercontent.com/emilk/egui/master/{}",
|
"https://raw.githubusercontent.com/emilk/egui/master/{}",
|
||||||
file!()
|
file!()
|
||||||
);
|
);
|
||||||
trigger_fetch = true;
|
trigger_fetch = true;
|
||||||
}
|
}
|
||||||
if ui.button("Random image").clicked {
|
if ui.button("Random image").clicked() {
|
||||||
let seed = ui.input().time;
|
let seed = ui.input().time;
|
||||||
let width = 640;
|
let width = 640;
|
||||||
let height = 480;
|
let height = 480;
|
||||||
|
@ -170,7 +170,7 @@ fn ui_resouce(
|
||||||
|
|
||||||
if let Some(text) = &response.text {
|
if let Some(text) = &response.text {
|
||||||
let tooltip = "Click to copy the response body";
|
let tooltip = "Click to copy the response body";
|
||||||
if ui.button("📋").on_hover_text(tooltip).clicked {
|
if ui.button("📋").on_hover_text(tooltip).clicked() {
|
||||||
ui.output().copied_text = text.clone();
|
ui.output().copied_text = text.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,9 +79,9 @@ impl FrameHistory {
|
||||||
let rect = rect.shrink(4.0);
|
let rect = rect.shrink(4.0);
|
||||||
let line_stroke = Stroke::new(1.0, Color32::from_additive_luminance(128));
|
let line_stroke = Stroke::new(1.0, Color32::from_additive_luminance(128));
|
||||||
|
|
||||||
if let Some(mouse_pos) = ui.input().mouse.pos {
|
if let Some(pointer_pos) = ui.input().pointer.tooltip_pos() {
|
||||||
if rect.contains(mouse_pos) {
|
if rect.contains(pointer_pos) {
|
||||||
let y = mouse_pos.y;
|
let y = pointer_pos.y;
|
||||||
shapes.push(Shape::line_segment(
|
shapes.push(Shape::line_segment(
|
||||||
[pos2(rect.left(), y), pos2(rect.right(), y)],
|
[pos2(rect.left(), y), pos2(rect.right(), y)],
|
||||||
line_stroke,
|
line_stroke,
|
||||||
|
|
|
@ -73,7 +73,7 @@ impl epi::App for WrapApp {
|
||||||
for (anchor, app) in self.apps.iter_mut() {
|
for (anchor, app) in self.apps.iter_mut() {
|
||||||
if ui
|
if ui
|
||||||
.selectable_label(self.selected_anchor == anchor, app.name())
|
.selectable_label(self.selected_anchor == anchor, app.name())
|
||||||
.clicked
|
.clicked()
|
||||||
{
|
{
|
||||||
self.selected_anchor = anchor.to_owned();
|
self.selected_anchor = anchor.to_owned();
|
||||||
if frame.is_web() {
|
if frame.is_web() {
|
||||||
|
@ -86,7 +86,7 @@ impl epi::App for WrapApp {
|
||||||
if false {
|
if false {
|
||||||
// TODO: fix the overlap on small screens
|
// TODO: fix the overlap on small screens
|
||||||
if let Some(seconds_since_midnight) = frame.info().seconds_since_midnight {
|
if let Some(seconds_since_midnight) = frame.info().seconds_since_midnight {
|
||||||
if clock_button(ui, seconds_since_midnight).clicked {
|
if clock_button(ui, seconds_since_midnight).clicked() {
|
||||||
self.selected_anchor = "clock".to_owned();
|
self.selected_anchor = "clock".to_owned();
|
||||||
if frame.is_web() {
|
if frame.is_web() {
|
||||||
ui.output().open_url = Some("#clock".to_owned());
|
ui.output().open_url = Some("#clock".to_owned());
|
||||||
|
@ -234,11 +234,11 @@ impl BackendPanel {
|
||||||
if ui
|
if ui
|
||||||
.button("📱 Phone Size")
|
.button("📱 Phone Size")
|
||||||
.on_hover_text("Resize the window to be small like a phone.")
|
.on_hover_text("Resize the window to be small like a phone.")
|
||||||
.clicked
|
.clicked()
|
||||||
{
|
{
|
||||||
frame.set_window_size(egui::Vec2::new(375.0, 812.0)); // iPhone 12 mini
|
frame.set_window_size(egui::Vec2::new(375.0, 812.0)); // iPhone 12 mini
|
||||||
}
|
}
|
||||||
if ui.button("Quit").clicked {
|
if ui.button("Quit").clicked() {
|
||||||
frame.quit();
|
frame.quit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -275,7 +275,7 @@ impl BackendPanel {
|
||||||
"Reset scale to native value ({:.1})",
|
"Reset scale to native value ({:.1})",
|
||||||
native_pixels_per_point
|
native_pixels_per_point
|
||||||
))
|
))
|
||||||
.clicked
|
.clicked()
|
||||||
{
|
{
|
||||||
*pixels_per_point = native_pixels_per_point;
|
*pixels_per_point = native_pixels_per_point;
|
||||||
}
|
}
|
||||||
|
@ -283,7 +283,7 @@ impl BackendPanel {
|
||||||
});
|
});
|
||||||
|
|
||||||
// We wait until mouse release to activate:
|
// We wait until mouse release to activate:
|
||||||
if ui.ctx().is_using_mouse() {
|
if ui.ctx().is_using_pointer() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(*pixels_per_point)
|
Some(*pixels_per_point)
|
||||||
|
|
|
@ -29,12 +29,14 @@ use {
|
||||||
pub use clipboard::ClipboardContext; // TODO: remove
|
pub use clipboard::ClipboardContext; // TODO: remove
|
||||||
|
|
||||||
pub struct GliumInputState {
|
pub struct GliumInputState {
|
||||||
|
pub pointer_pos_in_points: Option<Pos2>,
|
||||||
pub raw: egui::RawInput,
|
pub raw: egui::RawInput,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GliumInputState {
|
impl GliumInputState {
|
||||||
pub fn from_pixels_per_point(pixels_per_point: f32) -> Self {
|
pub fn from_pixels_per_point(pixels_per_point: f32) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
pointer_pos_in_points: Default::default(),
|
||||||
raw: egui::RawInput {
|
raw: egui::RawInput {
|
||||||
pixels_per_point: Some(pixels_per_point),
|
pixels_per_point: Some(pixels_per_point),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -52,23 +54,38 @@ pub fn input_to_egui(
|
||||||
use glutin::event::WindowEvent;
|
use glutin::event::WindowEvent;
|
||||||
match event {
|
match event {
|
||||||
WindowEvent::CloseRequested | WindowEvent::Destroyed => *control_flow = ControlFlow::Exit,
|
WindowEvent::CloseRequested | WindowEvent::Destroyed => *control_flow = ControlFlow::Exit,
|
||||||
WindowEvent::MouseInput { state, .. } => {
|
WindowEvent::MouseInput { state, button, .. } => {
|
||||||
input_state.raw.mouse_down = state == glutin::event::ElementState::Pressed;
|
if let Some(pos_in_points) = input_state.pointer_pos_in_points {
|
||||||
|
if let Some(button) = translate_mouse_button(button) {
|
||||||
|
input_state.raw.events.push(egui::Event::PointerButton {
|
||||||
|
pos: pos_in_points,
|
||||||
|
button,
|
||||||
|
pressed: state == glutin::event::ElementState::Pressed,
|
||||||
|
modifiers: input_state.raw.modifiers,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
WindowEvent::CursorMoved {
|
WindowEvent::CursorMoved {
|
||||||
position: pos_in_pixels,
|
position: pos_in_pixels,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
input_state.raw.mouse_pos = Some(pos2(
|
let pos_in_points = pos2(
|
||||||
pos_in_pixels.x as f32 / input_state.raw.pixels_per_point.unwrap(),
|
pos_in_pixels.x as f32 / input_state.raw.pixels_per_point.unwrap(),
|
||||||
pos_in_pixels.y as f32 / input_state.raw.pixels_per_point.unwrap(),
|
pos_in_pixels.y as f32 / input_state.raw.pixels_per_point.unwrap(),
|
||||||
));
|
);
|
||||||
|
input_state.pointer_pos_in_points = Some(pos_in_points);
|
||||||
|
input_state
|
||||||
|
.raw
|
||||||
|
.events
|
||||||
|
.push(egui::Event::PointerMoved(pos_in_points));
|
||||||
}
|
}
|
||||||
WindowEvent::CursorLeft { .. } => {
|
WindowEvent::CursorLeft { .. } => {
|
||||||
input_state.raw.mouse_pos = None;
|
input_state.pointer_pos_in_points = None;
|
||||||
|
input_state.raw.events.push(egui::Event::PointerGone);
|
||||||
}
|
}
|
||||||
WindowEvent::ReceivedCharacter(ch) => {
|
WindowEvent::ReceivedCharacter(ch) => {
|
||||||
if printable_char(ch)
|
if is_printable_char(ch)
|
||||||
&& !input_state.raw.modifiers.ctrl
|
&& !input_state.raw.modifiers.ctrl
|
||||||
&& !input_state.raw.modifiers.mac_cmd
|
&& !input_state.raw.modifiers.mac_cmd
|
||||||
{
|
{
|
||||||
|
@ -79,6 +96,7 @@ pub fn input_to_egui(
|
||||||
if let Some(keycode) = input.virtual_keycode {
|
if let Some(keycode) = input.virtual_keycode {
|
||||||
let pressed = input.state == glutin::event::ElementState::Pressed;
|
let pressed = input.state == glutin::event::ElementState::Pressed;
|
||||||
|
|
||||||
|
// We could also use `WindowEvent::ModifiersChanged` instead, I guess.
|
||||||
if matches!(keycode, VirtualKeyCode::LAlt | VirtualKeyCode::RAlt) {
|
if matches!(keycode, VirtualKeyCode::LAlt | VirtualKeyCode::RAlt) {
|
||||||
input_state.raw.modifiers.alt = pressed;
|
input_state.raw.modifiers.alt = pressed;
|
||||||
}
|
}
|
||||||
|
@ -157,7 +175,7 @@ pub fn input_to_egui(
|
||||||
/// Ignore those.
|
/// Ignore those.
|
||||||
/// We also ignore '\r', '\n', '\t'.
|
/// We also ignore '\r', '\n', '\t'.
|
||||||
/// Newlines are handled by the `Key::Enter` event.
|
/// Newlines are handled by the `Key::Enter` event.
|
||||||
fn printable_char(chr: char) -> bool {
|
fn is_printable_char(chr: char) -> bool {
|
||||||
let is_in_private_use_area = '\u{e000}' <= chr && chr <= '\u{f8ff}'
|
let is_in_private_use_area = '\u{e000}' <= chr && chr <= '\u{f8ff}'
|
||||||
|| '\u{f0000}' <= chr && chr <= '\u{ffffd}'
|
|| '\u{f0000}' <= chr && chr <= '\u{ffffd}'
|
||||||
|| '\u{100000}' <= chr && chr <= '\u{10fffd}';
|
|| '\u{100000}' <= chr && chr <= '\u{10fffd}';
|
||||||
|
@ -165,6 +183,15 @@ fn printable_char(chr: char) -> bool {
|
||||||
!is_in_private_use_area && !chr.is_ascii_control()
|
!is_in_private_use_area && !chr.is_ascii_control()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn translate_mouse_button(button: glutin::event::MouseButton) -> Option<egui::PointerButton> {
|
||||||
|
match button {
|
||||||
|
glutin::event::MouseButton::Left => Some(egui::PointerButton::Primary),
|
||||||
|
glutin::event::MouseButton::Right => Some(egui::PointerButton::Secondary),
|
||||||
|
glutin::event::MouseButton::Middle => Some(egui::PointerButton::Middle),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn translate_virtual_key_code(key: VirtualKeyCode) -> Option<egui::Key> {
|
pub fn translate_virtual_key_code(key: VirtualKeyCode) -> Option<egui::Key> {
|
||||||
use VirtualKeyCode::*;
|
use VirtualKeyCode::*;
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Added ⭐
|
||||||
|
|
||||||
|
* Right-clicks will no longer open browser context menu.
|
||||||
|
|
||||||
|
### Fixed ⭐
|
||||||
|
|
||||||
|
* Fix a bug where one couldn't select items in a combo box on a touch screen.
|
||||||
|
|
||||||
|
|
||||||
## 0.8.0 - 2021-01-17
|
## 0.8.0 - 2021-01-17
|
||||||
|
|
||||||
|
|
|
@ -81,6 +81,9 @@ pub struct WebInput {
|
||||||
/// Is this a touch screen? If so, we ignore mouse events.
|
/// Is this a touch screen? If so, we ignore mouse events.
|
||||||
pub is_touch: bool,
|
pub is_touch: bool,
|
||||||
|
|
||||||
|
/// Required because we don't get a position on touched
|
||||||
|
pub latest_touch_pos: Option<egui::Pos2>,
|
||||||
|
|
||||||
pub raw: egui::RawInput,
|
pub raw: egui::RawInput,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -97,6 +97,15 @@ pub fn pos_from_mouse_event(canvas_id: &str, event: &web_sys::MouseEvent) -> egu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn button_from_mouse_event(event: &web_sys::MouseEvent) -> Option<egui::PointerButton> {
|
||||||
|
match event.button() {
|
||||||
|
0 => Some(egui::PointerButton::Primary),
|
||||||
|
1 => Some(egui::PointerButton::Middle),
|
||||||
|
2 => Some(egui::PointerButton::Secondary),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn pos_from_touch_event(event: &web_sys::TouchEvent) -> egui::Pos2 {
|
pub fn pos_from_touch_event(event: &web_sys::TouchEvent) -> egui::Pos2 {
|
||||||
let t = event.touches().get(0).unwrap();
|
let t = event.touches().get(0).unwrap();
|
||||||
egui::Pos2 {
|
egui::Pos2 {
|
||||||
|
@ -599,19 +608,40 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
let canvas = canvas_element(runner_ref.0.lock().canvas_id()).unwrap();
|
let canvas = canvas_element(runner_ref.0.lock().canvas_id()).unwrap();
|
||||||
|
|
||||||
|
{
|
||||||
|
// By default, right-clicks open a context menu.
|
||||||
|
// We don't want to do that (right clicks is handled by egui):
|
||||||
|
let event_name = "contextmenu";
|
||||||
|
let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
|
||||||
|
event.prevent_default();
|
||||||
|
}) as Box<dyn FnMut(_)>);
|
||||||
|
canvas.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?;
|
||||||
|
closure.forget();
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let event_name = "mousedown";
|
let event_name = "mousedown";
|
||||||
let runner_ref = runner_ref.clone();
|
let runner_ref = runner_ref.clone();
|
||||||
let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
|
let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
|
||||||
let mut runner_lock = runner_ref.0.lock();
|
let mut runner_lock = runner_ref.0.lock();
|
||||||
if !runner_lock.input.is_touch {
|
if !runner_lock.input.is_touch {
|
||||||
runner_lock.input.raw.mouse_pos =
|
if let Some(button) = button_from_mouse_event(&event) {
|
||||||
Some(pos_from_mouse_event(runner_lock.canvas_id(), &event));
|
let pos = pos_from_mouse_event(runner_lock.canvas_id(), &event);
|
||||||
runner_lock.input.raw.mouse_down = true;
|
let modifiers = runner_lock.input.raw.modifiers;
|
||||||
runner_lock.logic().unwrap(); // in case we get "mouseup" the same frame. TODO: handle via events instead
|
runner_lock
|
||||||
runner_lock.needs_repaint.set_true();
|
.input
|
||||||
event.stop_propagation();
|
.raw
|
||||||
event.prevent_default();
|
.events
|
||||||
|
.push(egui::Event::PointerButton {
|
||||||
|
pos,
|
||||||
|
button,
|
||||||
|
pressed: true,
|
||||||
|
modifiers,
|
||||||
|
});
|
||||||
|
runner_lock.needs_repaint.set_true();
|
||||||
|
event.stop_propagation();
|
||||||
|
event.prevent_default();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}) as Box<dyn FnMut(_)>);
|
}) as Box<dyn FnMut(_)>);
|
||||||
canvas.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?;
|
canvas.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?;
|
||||||
|
@ -624,8 +654,12 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
|
||||||
let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
|
let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
|
||||||
let mut runner_lock = runner_ref.0.lock();
|
let mut runner_lock = runner_ref.0.lock();
|
||||||
if !runner_lock.input.is_touch {
|
if !runner_lock.input.is_touch {
|
||||||
runner_lock.input.raw.mouse_pos =
|
let pos = pos_from_mouse_event(runner_lock.canvas_id(), &event);
|
||||||
Some(pos_from_mouse_event(runner_lock.canvas_id(), &event));
|
runner_lock
|
||||||
|
.input
|
||||||
|
.raw
|
||||||
|
.events
|
||||||
|
.push(egui::Event::PointerMoved(pos));
|
||||||
runner_lock.needs_repaint.set_true();
|
runner_lock.needs_repaint.set_true();
|
||||||
event.stop_propagation();
|
event.stop_propagation();
|
||||||
event.prevent_default();
|
event.prevent_default();
|
||||||
|
@ -641,12 +675,23 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
|
||||||
let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
|
let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
|
||||||
let mut runner_lock = runner_ref.0.lock();
|
let mut runner_lock = runner_ref.0.lock();
|
||||||
if !runner_lock.input.is_touch {
|
if !runner_lock.input.is_touch {
|
||||||
runner_lock.input.raw.mouse_pos =
|
if let Some(button) = button_from_mouse_event(&event) {
|
||||||
Some(pos_from_mouse_event(runner_lock.canvas_id(), &event));
|
let pos = pos_from_mouse_event(runner_lock.canvas_id(), &event);
|
||||||
runner_lock.input.raw.mouse_down = false;
|
let modifiers = runner_lock.input.raw.modifiers;
|
||||||
runner_lock.needs_repaint.set_true();
|
runner_lock
|
||||||
event.stop_propagation();
|
.input
|
||||||
event.prevent_default();
|
.raw
|
||||||
|
.events
|
||||||
|
.push(egui::Event::PointerButton {
|
||||||
|
pos,
|
||||||
|
button,
|
||||||
|
pressed: false,
|
||||||
|
modifiers,
|
||||||
|
});
|
||||||
|
runner_lock.needs_repaint.set_true();
|
||||||
|
event.stop_propagation();
|
||||||
|
event.prevent_default();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}) as Box<dyn FnMut(_)>);
|
}) as Box<dyn FnMut(_)>);
|
||||||
canvas.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?;
|
canvas.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?;
|
||||||
|
@ -659,7 +704,7 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
|
||||||
let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
|
let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
|
||||||
let mut runner_lock = runner_ref.0.lock();
|
let mut runner_lock = runner_ref.0.lock();
|
||||||
if !runner_lock.input.is_touch {
|
if !runner_lock.input.is_touch {
|
||||||
runner_lock.input.raw.mouse_pos = None;
|
runner_lock.input.raw.events.push(egui::Event::PointerGone);
|
||||||
runner_lock.needs_repaint.set_true();
|
runner_lock.needs_repaint.set_true();
|
||||||
event.stop_propagation();
|
event.stop_propagation();
|
||||||
event.prevent_default();
|
event.prevent_default();
|
||||||
|
@ -673,10 +718,21 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
|
||||||
let event_name = "touchstart";
|
let event_name = "touchstart";
|
||||||
let runner_ref = runner_ref.clone();
|
let runner_ref = runner_ref.clone();
|
||||||
let closure = Closure::wrap(Box::new(move |event: web_sys::TouchEvent| {
|
let closure = Closure::wrap(Box::new(move |event: web_sys::TouchEvent| {
|
||||||
|
let pos = pos_from_touch_event(&event);
|
||||||
let mut runner_lock = runner_ref.0.lock();
|
let mut runner_lock = runner_ref.0.lock();
|
||||||
|
runner_lock.input.latest_touch_pos = Some(pos);
|
||||||
runner_lock.input.is_touch = true;
|
runner_lock.input.is_touch = true;
|
||||||
runner_lock.input.raw.mouse_pos = Some(pos_from_touch_event(&event));
|
let modifiers = runner_lock.input.raw.modifiers;
|
||||||
runner_lock.input.raw.mouse_down = true;
|
runner_lock
|
||||||
|
.input
|
||||||
|
.raw
|
||||||
|
.events
|
||||||
|
.push(egui::Event::PointerButton {
|
||||||
|
pos,
|
||||||
|
button: egui::PointerButton::Primary,
|
||||||
|
pressed: true,
|
||||||
|
modifiers,
|
||||||
|
});
|
||||||
runner_lock.needs_repaint.set_true();
|
runner_lock.needs_repaint.set_true();
|
||||||
event.stop_propagation();
|
event.stop_propagation();
|
||||||
event.prevent_default();
|
event.prevent_default();
|
||||||
|
@ -689,9 +745,15 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
|
||||||
let event_name = "touchmove";
|
let event_name = "touchmove";
|
||||||
let runner_ref = runner_ref.clone();
|
let runner_ref = runner_ref.clone();
|
||||||
let closure = Closure::wrap(Box::new(move |event: web_sys::TouchEvent| {
|
let closure = Closure::wrap(Box::new(move |event: web_sys::TouchEvent| {
|
||||||
|
let pos = pos_from_touch_event(&event);
|
||||||
let mut runner_lock = runner_ref.0.lock();
|
let mut runner_lock = runner_ref.0.lock();
|
||||||
|
runner_lock.input.latest_touch_pos = Some(pos);
|
||||||
runner_lock.input.is_touch = true;
|
runner_lock.input.is_touch = true;
|
||||||
runner_lock.input.raw.mouse_pos = Some(pos_from_touch_event(&event));
|
runner_lock
|
||||||
|
.input
|
||||||
|
.raw
|
||||||
|
.events
|
||||||
|
.push(egui::Event::PointerMoved(pos));
|
||||||
runner_lock.needs_repaint.set_true();
|
runner_lock.needs_repaint.set_true();
|
||||||
event.stop_propagation();
|
event.stop_propagation();
|
||||||
event.prevent_default();
|
event.prevent_default();
|
||||||
|
@ -706,12 +768,25 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
|
||||||
let closure = Closure::wrap(Box::new(move |event: web_sys::TouchEvent| {
|
let closure = Closure::wrap(Box::new(move |event: web_sys::TouchEvent| {
|
||||||
let mut runner_lock = runner_ref.0.lock();
|
let mut runner_lock = runner_ref.0.lock();
|
||||||
runner_lock.input.is_touch = true;
|
runner_lock.input.is_touch = true;
|
||||||
runner_lock.input.raw.mouse_down = false; // First release mouse to click...
|
if let Some(pos) = runner_lock.input.latest_touch_pos {
|
||||||
runner_lock.logic().unwrap(); // ...do the clicking... (TODO: handle via events instead)
|
let modifiers = runner_lock.input.raw.modifiers;
|
||||||
runner_lock.input.raw.mouse_pos = None; // ...remove hover effect
|
// First release mouse to click:
|
||||||
runner_lock.needs_repaint.set_true();
|
runner_lock
|
||||||
event.stop_propagation();
|
.input
|
||||||
event.prevent_default();
|
.raw
|
||||||
|
.events
|
||||||
|
.push(egui::Event::PointerButton {
|
||||||
|
pos,
|
||||||
|
button: egui::PointerButton::Primary,
|
||||||
|
pressed: false,
|
||||||
|
modifiers,
|
||||||
|
});
|
||||||
|
// Then remove hover effect:
|
||||||
|
runner_lock.input.raw.events.push(egui::Event::PointerGone);
|
||||||
|
runner_lock.needs_repaint.set_true();
|
||||||
|
event.stop_propagation();
|
||||||
|
event.prevent_default();
|
||||||
|
}
|
||||||
}) as Box<dyn FnMut(_)>);
|
}) as Box<dyn FnMut(_)>);
|
||||||
canvas.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?;
|
canvas.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?;
|
||||||
closure.forget();
|
closure.forget();
|
||||||
|
|
|
@ -21,6 +21,9 @@ pub const fn vec2(x: f32, y: f32) -> Vec2 {
|
||||||
Vec2 { x, y }
|
Vec2 { x, y }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Compatibility and convenience conversions to and from [f32; 2]:
|
||||||
|
|
||||||
impl From<[f32; 2]> for Vec2 {
|
impl From<[f32; 2]> for Vec2 {
|
||||||
fn from(v: [f32; 2]) -> Self {
|
fn from(v: [f32; 2]) -> Self {
|
||||||
Self { x: v[0], y: v[1] }
|
Self { x: v[0], y: v[1] }
|
||||||
|
@ -33,6 +36,47 @@ impl From<&[f32; 2]> for Vec2 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Vec2> for [f32; 2] {
|
||||||
|
fn from(v: Vec2) -> Self {
|
||||||
|
[v.x, v.y]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Vec2> for [f32; 2] {
|
||||||
|
fn from(v: &Vec2) -> Self {
|
||||||
|
[v.x, v.y]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Compatibility and convenience conversions to and from (f32, f32):
|
||||||
|
|
||||||
|
impl From<(f32, f32)> for Vec2 {
|
||||||
|
fn from(v: (f32, f32)) -> Self {
|
||||||
|
Self { x: v.0, y: v.1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&(f32, f32)> for Vec2 {
|
||||||
|
fn from(v: &(f32, f32)) -> Self {
|
||||||
|
Self { x: v.0, y: v.1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec2> for (f32, f32) {
|
||||||
|
fn from(v: Vec2) -> Self {
|
||||||
|
(v.x, v.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Vec2> for (f32, f32) {
|
||||||
|
fn from(v: &Vec2) -> Self {
|
||||||
|
(v.x, v.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
impl Vec2 {
|
impl Vec2 {
|
||||||
pub const X: Vec2 = Vec2 { x: 1.0, y: 0.0 };
|
pub const X: Vec2 = Vec2 { x: 1.0, y: 0.0 };
|
||||||
pub const Y: Vec2 = Vec2 { x: 0.0, y: 1.0 };
|
pub const Y: Vec2 = Vec2 { x: 0.0, y: 1.0 };
|
||||||
|
|
Loading…
Reference in a new issue