# HG changeset patch # User Emmanuel Gil Peyrot # Date 1604243309 -3600 # Node ID 97e543f50f62dfae01515110012bcf0a25bd76f2 # Parent d43c31aff57cc845b292cba3930e343c68238986 Split GTK UI into another module. diff --git a/src/gtk.rs b/src/gtk.rs new file mode 100644 --- /dev/null +++ b/src/gtk.rs @@ -0,0 +1,190 @@ +// Tablet emulator, for people who don’t own one +// Copyright © 2020 Emmanuel Gil Peyrot +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use gio::prelude::*; +use glib::clone; +use gtk::prelude::*; + +use std::env::args; +use std::io::ErrorKind; +use std::sync::Arc; +use input_linux::Key; + +use crate::{ + state::State, + DEFAULT_WIDTH, DEFAULT_HEIGHT, +}; + +fn build_main_menu(application: >k::Application) { + let quit = gio::SimpleAction::new("quit", None); + application.set_accels_for_action("app.quit", &["q"]); + application.add_action(&quit); + quit.connect_activate(clone!(@weak application => move |_, _| application.quit())); + + let about = gio::SimpleAction::new("about", None); + application.add_action(&about); + about.connect_activate(|_, _| { + let about = gtk::AboutDialog::new(); + about.set_program_name("TabletEmu"); + about.set_logo_icon_name(Some("input-tablet")); + about.set_website(Some("https://hg.linkmauve.fr/tablet-emu")); + about.set_version(Some("0.1")); + about.set_license_type(gtk::License::Agpl30); + about.set_copyright(Some("© 2020 Emmanuel Gil Peyrot ")); + about.run(); + unsafe { + about.destroy(); + } + }); + + let menu = gio::Menu::new(); + { + let file = gio::Menu::new(); + file.append(Some("_Quit"), Some("app.quit")); + menu.append_submenu(Some("_File"), &file); + } + { + let help = gio::Menu::new(); + help.append(Some("_About"), Some("app.about")); + menu.append_submenu(Some("_Help"), &help); + } + application.set_menubar(Some(&menu)); +} + +fn build_ui(application: >k::Application) { + build_main_menu(application); + + let state = match State::new() { + Ok(state) => state, + Err(err) => { + match err.kind() { + ErrorKind::NotFound => { + eprintln!("Couldn’t find /dev/uinput: {}", err); + eprintln!("Maybe you forgot to `modprobe uinput`?"); + } + ErrorKind::PermissionDenied => { + eprintln!("Couldn’t open /dev/uinput for writing: {}", err); + eprintln!("Maybe you aren’t allowed to create input devices?"); + } + _ => eprintln!("Couldn’t open /dev/uinput for writing: {}", err), + } + std::process::exit(1); + } + }; + + let window = gtk::ApplicationWindow::new(application); + window.set_title("tablet-emu"); + window.set_position(gtk::WindowPosition::Center); + + let hbox = gtk::Box::new(gtk::Orientation::Horizontal, 0); + let tools_box = gtk::Box::new(gtk::Orientation::Vertical, 0); + + macro_rules! impl_tool { + ($tool:tt) => { + let tool = gtk::Button::with_mnemonic($tool); + let state_weak = Arc::downgrade(&state); + tool.connect_clicked(move |b| { + let state = state_weak.upgrade().unwrap(); + let mut state = state.lock().unwrap(); + let tool = match b.get_label().unwrap().as_str() { + "_Pen" => Key::ButtonToolPen, + "_Rubber" => Key::ButtonToolRubber, + "_Brush" => Key::ButtonToolBrush, + "P_encil" => Key::ButtonToolPencil, + "_Airbrush" => Key::ButtonToolAirbrush, + _ => unreachable!(), + }; + state.select_tool(tool); + }); + tools_box.add(&tool); + }; + }; + impl_tool!("_Pen"); + impl_tool!("_Rubber"); + impl_tool!("_Brush"); + impl_tool!("P_encil"); + impl_tool!("_Airbrush"); + + let drawing_area = gtk::DrawingArea::new(); + drawing_area.set_size_request(DEFAULT_WIDTH, DEFAULT_HEIGHT); + drawing_area.set_hexpand(true); + drawing_area.set_events( + gdk::EventMask::BUTTON_PRESS_MASK + | gdk::EventMask::BUTTON_RELEASE_MASK + | gdk::EventMask::POINTER_MOTION_MASK, + ); + let state_weak = Arc::downgrade(&state); + drawing_area.connect_configure_event(move |_, event| { + let state = state_weak.upgrade().unwrap(); + let mut state = state.lock().unwrap(); + state.set_size(event.get_size()); + true + }); + let state_weak = Arc::downgrade(&state); + drawing_area.connect_button_press_event(move |_, event| { + if event.get_button() != 1 { + return Inhibit(false); + } + + let state = state_weak.upgrade().unwrap(); + let mut state = state.lock().unwrap(); + let (x, y) = event.get_position(); + state.press(x, y).unwrap(); + Inhibit(false) + }); + let state_weak = Arc::downgrade(&state); + drawing_area.connect_button_release_event(move |_, event| { + if event.get_button() != 1 { + return Inhibit(false); + } + + let state = state_weak.upgrade().unwrap(); + let mut state = state.lock().unwrap(); + let (x, y) = event.get_position(); + state.release(x, y).unwrap(); + Inhibit(false) + }); + drawing_area.connect_motion_notify_event(move |_, event| { + let mut state = state.lock().unwrap(); + let (x, y) = event.get_position(); + state.motion(x, y).unwrap(); + Inhibit(false) + }); + drawing_area.connect_draw(move |_, ctx| { + //println!("drawing {}", drawing_area); + ctx.set_source_rgb(1., 0., 0.); + ctx.set_operator(cairo::Operator::Screen); + ctx.paint(); + Inhibit(false) + }); + + hbox.add(&tools_box); + hbox.add(&drawing_area); + + window.add(&hbox); + + window.show_all(); +} + +pub fn main() { + let application = gtk::Application::new( + Some("fr.linkmauve.TabletEmu"), + gio::ApplicationFlags::empty(), + ) + .expect("Initialisation failed…"); + application.connect_activate(build_ui); + application.run(&args().collect::>()); +} diff --git a/src/main.rs b/src/main.rs --- a/src/main.rs +++ b/src/main.rs @@ -14,182 +14,16 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use gio::prelude::*; -use glib::clone; -use gtk::prelude::*; - -use std::env::args; -use std::io::ErrorKind; -use std::sync::Arc; -use input_linux::Key; - +mod gtk; mod uinput; mod state; -use state::State; - -const WIDTH: i32 = 320; -const HEIGHT: i32 = 180; const MAX_X: i32 = 69920; const MAX_Y: i32 = 39980; -fn build_main_menu(application: >k::Application) { - let quit = gio::SimpleAction::new("quit", None); - application.set_accels_for_action("app.quit", &["q"]); - application.add_action(&quit); - quit.connect_activate(clone!(@weak application => move |_, _| application.quit())); - - let about = gio::SimpleAction::new("about", None); - application.add_action(&about); - about.connect_activate(|_, _| { - let about = gtk::AboutDialog::new(); - about.set_program_name("TabletEmu"); - about.set_logo_icon_name(Some("input-tablet")); - about.set_website(Some("https://hg.linkmauve.fr/tablet-emu")); - about.set_version(Some("0.1")); - about.set_license_type(gtk::License::Agpl30); - about.set_copyright(Some("© 2020 Emmanuel Gil Peyrot ")); - about.run(); - unsafe { - about.destroy(); - } - }); - - let menu = gio::Menu::new(); - { - let file = gio::Menu::new(); - file.append(Some("_Quit"), Some("app.quit")); - menu.append_submenu(Some("_File"), &file); - } - { - let help = gio::Menu::new(); - help.append(Some("_About"), Some("app.about")); - menu.append_submenu(Some("_Help"), &help); - } - application.set_menubar(Some(&menu)); -} - -fn build_ui(application: >k::Application) { - build_main_menu(application); - - let state = match State::new() { - Ok(state) => state, - Err(err) => { - match err.kind() { - ErrorKind::NotFound => { - eprintln!("Couldn’t find /dev/uinput: {}", err); - eprintln!("Maybe you forgot to `modprobe uinput`?"); - } - ErrorKind::PermissionDenied => { - eprintln!("Couldn’t open /dev/uinput for writing: {}", err); - eprintln!("Maybe you aren’t allowed to create input devices?"); - } - _ => eprintln!("Couldn’t open /dev/uinput for writing: {}", err), - } - std::process::exit(1); - } - }; - - let window = gtk::ApplicationWindow::new(application); - window.set_title("tablet-emu"); - window.set_position(gtk::WindowPosition::Center); - - let hbox = gtk::Box::new(gtk::Orientation::Horizontal, 0); - let tools_box = gtk::Box::new(gtk::Orientation::Vertical, 0); - - macro_rules! impl_tool { - ($tool:tt) => { - let tool = gtk::Button::with_mnemonic($tool); - let state_weak = Arc::downgrade(&state); - tool.connect_clicked(move |b| { - let state = state_weak.upgrade().unwrap(); - let mut state = state.lock().unwrap(); - let tool = match b.get_label().unwrap().as_str() { - "_Pen" => Key::ButtonToolPen, - "_Rubber" => Key::ButtonToolRubber, - "_Brush" => Key::ButtonToolBrush, - "P_encil" => Key::ButtonToolPencil, - "_Airbrush" => Key::ButtonToolAirbrush, - _ => unreachable!(), - }; - state.select_tool(tool); - }); - tools_box.add(&tool); - }; - }; - impl_tool!("_Pen"); - impl_tool!("_Rubber"); - impl_tool!("_Brush"); - impl_tool!("P_encil"); - impl_tool!("_Airbrush"); - - let drawing_area = gtk::DrawingArea::new(); - drawing_area.set_size_request(WIDTH, HEIGHT); - drawing_area.set_hexpand(true); - drawing_area.set_events( - gdk::EventMask::BUTTON_PRESS_MASK - | gdk::EventMask::BUTTON_RELEASE_MASK - | gdk::EventMask::POINTER_MOTION_MASK, - ); - let state_weak = Arc::downgrade(&state); - drawing_area.connect_configure_event(move |_, event| { - let state = state_weak.upgrade().unwrap(); - let mut state = state.lock().unwrap(); - state.set_size(event.get_size()); - true - }); - let state_weak = Arc::downgrade(&state); - drawing_area.connect_button_press_event(move |_, event| { - if event.get_button() != 1 { - return Inhibit(false); - } - - let state = state_weak.upgrade().unwrap(); - let mut state = state.lock().unwrap(); - let (x, y) = event.get_position(); - state.press(x, y).unwrap(); - Inhibit(false) - }); - let state_weak = Arc::downgrade(&state); - drawing_area.connect_button_release_event(move |_, event| { - if event.get_button() != 1 { - return Inhibit(false); - } - - let state = state_weak.upgrade().unwrap(); - let mut state = state.lock().unwrap(); - let (x, y) = event.get_position(); - state.release(x, y).unwrap(); - Inhibit(false) - }); - drawing_area.connect_motion_notify_event(move |_, event| { - let mut state = state.lock().unwrap(); - let (x, y) = event.get_position(); - state.motion(x, y).unwrap(); - Inhibit(false) - }); - drawing_area.connect_draw(move |_, ctx| { - //println!("drawing {}", drawing_area); - ctx.set_source_rgb(1., 0., 0.); - ctx.set_operator(cairo::Operator::Screen); - ctx.paint(); - Inhibit(false) - }); - - hbox.add(&tools_box); - hbox.add(&drawing_area); - - window.add(&hbox); - - window.show_all(); -} +const DEFAULT_WIDTH: i32 = 320; +const DEFAULT_HEIGHT: i32 = 180; fn main() { - let application = gtk::Application::new( - Some("fr.linkmauve.TabletEmu"), - gio::ApplicationFlags::empty(), - ) - .expect("Initialisation failed…"); - application.connect_activate(build_ui); - application.run(&args().collect::>()); + gtk::main(); } diff --git a/src/state.rs b/src/state.rs --- a/src/state.rs +++ b/src/state.rs @@ -18,7 +18,7 @@ use std::fs::File; use std::sync::{Arc, Mutex}; use input_linux::{AbsoluteAxis, Key, MiscKind, SynchronizeKind, UInputHandle}; -use crate::{WIDTH, HEIGHT, MAX_X, MAX_Y}; +use crate::{DEFAULT_WIDTH, DEFAULT_HEIGHT, MAX_X, MAX_Y}; use crate::uinput::{create_uinput_device, input_axis_new, input_misc_new, input_key_new, input_synchronize_new}; pub struct State { @@ -40,8 +40,8 @@ impl State { Ok(Arc::new(Mutex::new(State { dev, - width: WIDTH as f64, - height: HEIGHT as f64, + width: DEFAULT_WIDTH as f64, + height: DEFAULT_HEIGHT as f64, selected_tool: Key::ButtonToolPen, pressed: false, })))