Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -235,3 +235,7 @@ required-features = ["v4_10"]
[[bin]]
name = "virtual_methods"
path = "virtual_methods/main.rs"

[[bin]]
name = "push_button"
path = "push_button/main.rs"
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ cargo run --bin basics
- [ListBox: StringList with Sorter](./list_box_sort_stringlist/)
- [ListView: Applications Launcher](./list_view_apps_launcher/)
- [Menubar](./menubar/)
- [Push Button](./push_button/)
- [Rotation Bin](./rotation_bin/)
- [Scale](./scale/)
- [Scale Bin](./scale_bin/)
Expand Down
22 changes: 22 additions & 0 deletions examples/push_button/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Push Button

This example demonstrates how to handle "press" and "release" events on a button
using both mouse and keyboard interactions.

It addresses common issues like:

- Detecting when a button is released (not just clicked).
- Handling keyboard "key repeat" to prevent flickering.
- Using `EventControllerKey` and `GestureClick` together.

## Previews

| Mouse Interaction | Keyboard Interaction |
| :--------------------------------------: | :-----------------------------------------: |
| ![Mouse preview](assets/screenshot1.gif) | ![Keyboard preview](assets/screenshot2.gif) |

## Usage

- Press and hold the button with the **mouse** or the **Space bar**.
- The label changes to "Release me!" while held.
- The label reverts to "Press me!" when released.
Binary file added examples/push_button/assets/screenshot1.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/push_button/assets/screenshot2.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
127 changes: 127 additions & 0 deletions examples/push_button/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
use gtk::glib;
use gtk::prelude::*;
use std::cell::Cell;
use std::io::{self, Write};
use std::rc::Rc;

fn main() -> glib::ExitCode {
let application = gtk::Application::builder()
.application_id("com.github.gtk-rs.examples.push_button")
.build();
application.connect_activate(build_ui);
application.run()
}

// Helper function to update the terminal status line
fn update_status(status: &str) {
// We use a fixed width to clear the previous line, then return the cursor
// to the end of the actual text for a better visual experience.
print!("\r{status:<30}\r{status}");
let _ = io::stdout().flush();
}

fn build_ui(application: &gtk::Application) {
let window = gtk::ApplicationWindow::builder()
.application(application)
.title("Push Button Example")
.default_width(300)
.default_height(100)
.build();

let push_button = gtk::Button::builder()
.label("Press me!")
.margin_top(20)
.margin_bottom(20)
.margin_start(20)
.margin_end(20)
.build();

// State to track if the button is currently "pressed" to ignore key repeat
let is_pressed = Rc::new(Cell::new(false));

// Handle Mouse Events
let gesture_click = gtk::GestureClick::new();
gesture_click.connect_pressed(glib::clone!(
#[weak]
push_button,
#[strong]
is_pressed,
move |gesture, _n_press, _x, _y| {
gesture.set_state(gtk::EventSequenceState::Claimed);
is_pressed.set(true);
push_button.set_label("Release me!");
update_status("Button pressed (mouse)!");
}
));

gesture_click.connect_released(glib::clone!(
#[weak]
push_button,
#[strong]
is_pressed,
move |gesture, _n_press, _x, _y| {
gesture.set_state(gtk::EventSequenceState::Claimed);
is_pressed.set(false);
push_button.set_label("Press me!");
update_status("Button released!");
}
));

push_button.add_controller(gesture_click);

// Handle Keyboard Events
let controller_key = gtk::EventControllerKey::new();

let push_button_weak = push_button.downgrade();
let is_pressed_clone = is_pressed.clone();
controller_key.connect_key_pressed(move |_controller, keyval, _keycode, _state| {
if keyval != gtk::gdk::Key::space {
return glib::Propagation::Proceed;
}

let Some(push_button) = push_button_weak.upgrade() else {
return glib::Propagation::Proceed;
};

if !is_pressed_clone.get() {
is_pressed_clone.set(true);
push_button.set_label("Release me!");
// Set the ACTIVE state flag to give visual feedback (same as mouse click)
push_button.set_state_flags(gtk::StateFlags::ACTIVE, false);
update_status("Button pressed (keyboard)!");
}
glib::Propagation::Stop
});

let push_button_weak = push_button.downgrade();
let is_pressed_clone = is_pressed.clone();
controller_key.connect_key_released(move |_controller, keyval, _keycode, _state| {
if keyval != gtk::gdk::Key::space {
return;
}

let Some(push_button) = push_button_weak.upgrade() else {
return;
};

is_pressed_clone.set(false);
push_button.set_label("Press me!");
// Remove the ACTIVE state flag when the key is released
push_button.unset_state_flags(gtk::StateFlags::ACTIVE);
update_status("Button released!");
});

push_button.add_controller(controller_key);

window.set_child(Some(&push_button));

window.connect_close_request(move |_| {
print!("\r");
glib::Propagation::Proceed
});

// Set initial terminal status
update_status("Button released!");

window.present();
}
Loading