Skip to content

Commit

Permalink
Merge branch 'emilk:master' into patch15
Browse files Browse the repository at this point in the history
  • Loading branch information
rustbasic authored Mar 12, 2024
2 parents 1f5d8ca + f019032 commit 98b6ae4
Show file tree
Hide file tree
Showing 18 changed files with 463 additions and 31 deletions.
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Please keep pull requests small and focused. The smaller it is, the more likely

Most PR reviews are done by me, Emil, but I very much appreciate any help I can get reviewing PRs!

It is very easy to add complexity to a project, but remember that each line of code added is code that needs to be maintained in perpituity, so we have a high bar on what get merged!
It is very easy to add complexity to a project, but remember that each line of code added is code that needs to be maintained in perpetuity, so we have a high bar on what get merged!

When reviewing, we look for:
* The PR title and description should be helpful
Expand Down Expand Up @@ -123,7 +123,7 @@ with `Vec2::X` increasing to the right and `Vec2::Y` increasing downwards.

`egui` uses logical _points_ as its coordinate system.
Those related to physical _pixels_ by the `pixels_per_point` scale factor.
For example, a high-dpi screeen can have `pixels_per_point = 2.0`,
For example, a high-dpi screen can have `pixels_per_point = 2.0`,
meaning there are two physical screen pixels for each logical point.

Angles are in radians, and are measured clockwise from the X-axis, which has angle=0.
Expand Down
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions crates/eframe/src/epi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,24 @@ pub trait App {
fn persist_egui_memory(&self) -> bool {
true
}

/// A hook for manipulating or filtering raw input before it is processed by [`Self::update`].
///
/// This function provides a way to modify or filter input events before they are processed by egui.
///
/// It can be used to prevent specific keyboard shortcuts or mouse events from being processed by egui.
///
/// Additionally, it can be used to inject custom keyboard or mouse events into the input stream, which can be useful for implementing features like a virtual keyboard.
///
/// # Arguments
///
/// * `_ctx` - The context of the egui, which provides access to the current state of the egui.
/// * `_raw_input` - The raw input events that are about to be processed. This can be modified to change the input that egui processes.
///
/// # Note
///
/// This function does not return a value. Any changes to the input should be made directly to `_raw_input`.
fn raw_input_hook(&mut self, _ctx: &egui::Context, _raw_input: &mut egui::RawInput) {}
}

/// Selects the level of hardware graphics acceleration.
Expand Down
2 changes: 2 additions & 0 deletions crates/eframe/src/native/epi_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,8 @@ impl EpiIntegration {

let close_requested = raw_input.viewport().close_requested();

app.raw_input_hook(&self.egui_ctx, &mut raw_input);

let full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
if let Some(viewport_ui_cb) = viewport_ui_cb {
// Child viewport
Expand Down
12 changes: 9 additions & 3 deletions crates/eframe/src/web/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ pub(crate) fn install_document_events(runner_ref: &WebRunner) -> Result<(), JsVa
return;
}

let modifiers = modifiers_from_event(&event);
let modifiers = modifiers_from_kb_event(&event);
runner.input.raw.modifiers = modifiers;

let key = event.key();
Expand Down Expand Up @@ -158,7 +158,7 @@ pub(crate) fn install_document_events(runner_ref: &WebRunner) -> Result<(), JsVa
&document,
"keyup",
|event: web_sys::KeyboardEvent, runner| {
let modifiers = modifiers_from_event(&event);
let modifiers = modifiers_from_kb_event(&event);
runner.input.raw.modifiers = modifiers;
if let Some(key) = translate_key(&event.key()) {
runner.input.raw.events.push(egui::Event::Key {
Expand Down Expand Up @@ -301,6 +301,8 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu
&canvas,
"mousedown",
|event: web_sys::MouseEvent, runner: &mut AppRunner| {
let modifiers = modifiers_from_mouse_event(&event);
runner.input.raw.modifiers = modifiers;
if let Some(button) = button_from_mouse_event(&event) {
let pos = pos_from_mouse_event(runner.canvas_id(), &event);
let modifiers = runner.input.raw.modifiers;
Expand All @@ -327,6 +329,8 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu
&canvas,
"mousemove",
|event: web_sys::MouseEvent, runner| {
let modifiers = modifiers_from_mouse_event(&event);
runner.input.raw.modifiers = modifiers;
let pos = pos_from_mouse_event(runner.canvas_id(), &event);
runner.input.raw.events.push(egui::Event::PointerMoved(pos));
runner.needs_repaint.repaint_asap();
Expand All @@ -336,6 +340,8 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu
)?;

runner_ref.add_event_listener(&canvas, "mouseup", |event: web_sys::MouseEvent, runner| {
let modifiers = modifiers_from_mouse_event(&event);
runner.input.raw.modifiers = modifiers;
if let Some(button) = button_from_mouse_event(&event) {
let pos = pos_from_mouse_event(runner.canvas_id(), &event);
let modifiers = runner.input.raw.modifiers;
Expand Down Expand Up @@ -474,7 +480,7 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu

// Report a zoom event in case CTRL (on Windows or Linux) or CMD (on Mac) is pressed.
// This if-statement is equivalent to how `Modifiers.command` is determined in
// `modifiers_from_event()`, but we cannot directly use that fn for a [`WheelEvent`].
// `modifiers_from_kb_event()`, but we cannot directly use that fn for a [`WheelEvent`].
if event.ctrl_key() || event.meta_key() {
let factor = (delta.y / 200.0).exp();
runner.input.raw.events.push(egui::Event::Zoom(factor));
Expand Down
18 changes: 17 additions & 1 deletion crates/eframe/src/web/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,23 @@ pub fn translate_key(key: &str) -> Option<egui::Key> {
egui::Key::from_name(key)
}

pub fn modifiers_from_event(event: &web_sys::KeyboardEvent) -> egui::Modifiers {
pub fn modifiers_from_kb_event(event: &web_sys::KeyboardEvent) -> egui::Modifiers {
egui::Modifiers {
alt: event.alt_key(),
ctrl: event.ctrl_key(),
shift: event.shift_key(),

// Ideally we should know if we are running or mac or not,
// but this works good enough for now.
mac_cmd: event.meta_key(),

// Ideally we should know if we are running or mac or not,
// but this works good enough for now.
command: event.ctrl_key() || event.meta_key(),
}
}

pub fn modifiers_from_mouse_event(event: &web_sys::MouseEvent) -> egui::Modifiers {
egui::Modifiers {
alt: event.alt_key(),
ctrl: event.ctrl_key(),
Expand Down
21 changes: 10 additions & 11 deletions crates/egui/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ struct ViewportState {
#[derive(Clone, Debug)]
pub struct RepaintCause {
/// What file had the call that requested the repaint?
pub file: String,
pub file: &'static str,

/// What line number of the the call that requested the repaint?
pub line: u32,
Expand All @@ -269,7 +269,7 @@ impl RepaintCause {
pub fn new() -> Self {
let caller = Location::caller();
Self {
file: caller.file().to_owned(),
file: caller.file(),
line: caller.line(),
}
}
Expand Down Expand Up @@ -1018,12 +1018,7 @@ impl Context {
///
/// If the widget already exists, its state (sense, Rect, etc) will be updated.
#[allow(clippy::too_many_arguments)]
pub(crate) fn create_widget(&self, mut w: WidgetRect) -> Response {
if !w.enabled {
w.sense.click = false;
w.sense.drag = false;
}

pub(crate) fn create_widget(&self, w: WidgetRect) -> Response {
// Remember this widget
self.write(|ctx| {
let viewport = ctx.viewport();
Expand Down Expand Up @@ -1130,7 +1125,8 @@ impl Context {
let input = &viewport.input;
let memory = &mut ctx.memory;

if sense.click
if enabled
&& sense.click
&& memory.has_focus(id)
&& (input.key_pressed(Key::Space) || input.key_pressed(Key::Enter))
{
Expand All @@ -1139,7 +1135,10 @@ impl Context {
}

#[cfg(feature = "accesskit")]
if sense.click && input.has_accesskit_action_request(id, accesskit::Action::Default) {
if enabled
&& sense.click
&& input.has_accesskit_action_request(id, accesskit::Action::Default)
{
res.clicked[PointerButton::Primary as usize] = true;
}

Expand All @@ -1159,7 +1158,7 @@ impl Context {

for pointer_event in &input.pointer.pointer_events {
if let PointerEvent::Released { click, button } = pointer_event {
if sense.click && clicked {
if enabled && sense.click && clicked {
if let Some(click) = click {
res.clicked[*button as usize] = true;
res.double_clicked[*button as usize] = click.is_double();
Expand Down
10 changes: 10 additions & 0 deletions crates/egui/src/hit_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,16 @@ pub fn hit_test(
let top_layer = closest_hit.layer_id;
close.retain(|w| w.layer_id == top_layer);

// If the widget is disabled, treat it as if it isn't sensing anything.
// This simplifies the code in `hit_test_on_close` so it doesn't have to check
// the `enabled` flag everywhere:
for w in &mut close {
if !w.enabled {
w.sense.click = false;
w.sense.drag = false;
}
}

let pos_in_layer = pos_in_layers.get(&top_layer).copied().unwrap_or(pos);
let hits = hit_test_on_close(&close, pos_in_layer);

Expand Down
26 changes: 14 additions & 12 deletions crates/egui/src/interaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,18 +161,20 @@ pub(crate) fn interact(
if dragged.is_none() {
// Check if we started dragging something new:
if let Some(widget) = interaction.potential_drag_id.and_then(|id| widgets.get(id)) {
let is_dragged = if widget.sense.click && widget.sense.drag {
// This widget is sensitive to both clicks and drags.
// When the mouse first is pressed, it could be either,
// so we postpone the decision until we know.
input.pointer.is_decidedly_dragging()
} else {
// This widget is just sensitive to drags, so we can mark it as dragged right away:
widget.sense.drag
};

if is_dragged {
dragged = Some(widget.id);
if widget.enabled {
let is_dragged = if widget.sense.click && widget.sense.drag {
// This widget is sensitive to both clicks and drags.
// When the mouse first is pressed, it could be either,
// so we postpone the decision until we know.
input.pointer.is_decidedly_dragging()
} else {
// This widget is just sensitive to drags, so we can mark it as dragged right away:
widget.sense.drag
};

if is_dragged {
dragged = Some(widget.id);
}
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions crates/egui/src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ pub struct Response {
pub interact_rect: Rect,

/// The senses (click and/or drag) that the widget was interested in (if any).
///
/// Note: if [`Self::enabled`] is `false`, then
/// the widget _effectively_ doesn't sense anything,
/// but can still have the same `Sense`.
/// This is because the sense informs the styling of the widget,
/// but we don't want to change the style when a widget is disabled
/// (that is handled by the `Painter` directly).
pub sense: Sense,

/// Was the widget enabled?
Expand Down
7 changes: 7 additions & 0 deletions crates/egui/src/widget_rect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ pub struct WidgetRect {
pub interact_rect: Rect,

/// How the widget responds to interaction.
///
/// Note: if [`Self::enabled`] is `false`, then
/// the widget _effectively_ doesn't sense anything,
/// but can still have the same `Sense`.
/// This is because the sense informs the styling of the widget,
/// but we don't want to change the style when a widget is disabled
/// (that is handled by the `Painter` directly).
pub sense: Sense,

/// Is the widget enabled?
Expand Down
6 changes: 4 additions & 2 deletions crates/egui/src/widgets/text_edit/text_buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,12 @@ pub trait TextBuffer {
fn decrease_indentation(&mut self, ccursor: &mut CCursor) {
let line_start = find_line_start(self.as_str(), *ccursor);

let remove_len = if self.as_str()[line_start.index..].starts_with('\t') {
let remove_len = if self.as_str().chars().nth(line_start.index) == Some('\t') {
Some(1)
} else if self.as_str()[line_start.index..]
} else if self
.as_str()
.chars()
.skip(line_start.index)
.take(TAB_SIZE)
.all(|c| c == ' ')
{
Expand Down
1 change: 1 addition & 0 deletions crates/epaint/src/shape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,7 @@ impl Shape {
Self::Rect(rect_shape) => {
rect_shape.rect = transform * rect_shape.rect;
rect_shape.stroke.width *= transform.scaling;
rect_shape.rounding *= transform.scaling;
}
Self::Text(text_shape) => {
text_shape.pos = transform * text_shape.pos;
Expand Down
23 changes: 23 additions & 0 deletions examples/custom_keypad/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "custom_keypad"
version = "0.1.0"
authors = ["Varphone Wong <[email protected]>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.72"
publish = false


[dependencies]
eframe = { workspace = true, features = [
"default",
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }

# For image support:
egui_extras = { workspace = true, features = ["default", "image"] }

env_logger = { version = "0.10", default-features = false, features = [
"auto-color",
"humantime",
] }
7 changes: 7 additions & 0 deletions examples/custom_keypad/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Example showing how to implements a custom keypad.

```sh
cargo run -p custom_keypad
```

![](screenshot.png)
Binary file added examples/custom_keypad/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 98b6ae4

Please sign in to comment.