comparison src/gtk.rs @ 13:97e543f50f62

Split GTK UI into another module.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Sun, 01 Nov 2020 16:08:29 +0100
parents
children adab13145994
comparison
equal deleted inserted replaced
12:d43c31aff57c 13:97e543f50f62
1 // Tablet emulator, for people who don’t own one
2 // Copyright © 2020 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU Affero General Public License for more details.
13 //
14 // You should have received a copy of the GNU Affero General Public License
15 // along with this program. If not, see <https://www.gnu.org/licenses/>.
16
17 use gio::prelude::*;
18 use glib::clone;
19 use gtk::prelude::*;
20
21 use std::env::args;
22 use std::io::ErrorKind;
23 use std::sync::Arc;
24 use input_linux::Key;
25
26 use crate::{
27 state::State,
28 DEFAULT_WIDTH, DEFAULT_HEIGHT,
29 };
30
31 fn build_main_menu(application: &gtk::Application) {
32 let quit = gio::SimpleAction::new("quit", None);
33 application.set_accels_for_action("app.quit", &["<Control>q"]);
34 application.add_action(&quit);
35 quit.connect_activate(clone!(@weak application => move |_, _| application.quit()));
36
37 let about = gio::SimpleAction::new("about", None);
38 application.add_action(&about);
39 about.connect_activate(|_, _| {
40 let about = gtk::AboutDialog::new();
41 about.set_program_name("TabletEmu");
42 about.set_logo_icon_name(Some("input-tablet"));
43 about.set_website(Some("https://hg.linkmauve.fr/tablet-emu"));
44 about.set_version(Some("0.1"));
45 about.set_license_type(gtk::License::Agpl30);
46 about.set_copyright(Some("© 2020 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>"));
47 about.run();
48 unsafe {
49 about.destroy();
50 }
51 });
52
53 let menu = gio::Menu::new();
54 {
55 let file = gio::Menu::new();
56 file.append(Some("_Quit"), Some("app.quit"));
57 menu.append_submenu(Some("_File"), &file);
58 }
59 {
60 let help = gio::Menu::new();
61 help.append(Some("_About"), Some("app.about"));
62 menu.append_submenu(Some("_Help"), &help);
63 }
64 application.set_menubar(Some(&menu));
65 }
66
67 fn build_ui(application: &gtk::Application) {
68 build_main_menu(application);
69
70 let state = match State::new() {
71 Ok(state) => state,
72 Err(err) => {
73 match err.kind() {
74 ErrorKind::NotFound => {
75 eprintln!("Couldn’t find /dev/uinput: {}", err);
76 eprintln!("Maybe you forgot to `modprobe uinput`?");
77 }
78 ErrorKind::PermissionDenied => {
79 eprintln!("Couldn’t open /dev/uinput for writing: {}", err);
80 eprintln!("Maybe you aren’t allowed to create input devices?");
81 }
82 _ => eprintln!("Couldn’t open /dev/uinput for writing: {}", err),
83 }
84 std::process::exit(1);
85 }
86 };
87
88 let window = gtk::ApplicationWindow::new(application);
89 window.set_title("tablet-emu");
90 window.set_position(gtk::WindowPosition::Center);
91
92 let hbox = gtk::Box::new(gtk::Orientation::Horizontal, 0);
93 let tools_box = gtk::Box::new(gtk::Orientation::Vertical, 0);
94
95 macro_rules! impl_tool {
96 ($tool:tt) => {
97 let tool = gtk::Button::with_mnemonic($tool);
98 let state_weak = Arc::downgrade(&state);
99 tool.connect_clicked(move |b| {
100 let state = state_weak.upgrade().unwrap();
101 let mut state = state.lock().unwrap();
102 let tool = match b.get_label().unwrap().as_str() {
103 "_Pen" => Key::ButtonToolPen,
104 "_Rubber" => Key::ButtonToolRubber,
105 "_Brush" => Key::ButtonToolBrush,
106 "P_encil" => Key::ButtonToolPencil,
107 "_Airbrush" => Key::ButtonToolAirbrush,
108 _ => unreachable!(),
109 };
110 state.select_tool(tool);
111 });
112 tools_box.add(&tool);
113 };
114 };
115 impl_tool!("_Pen");
116 impl_tool!("_Rubber");
117 impl_tool!("_Brush");
118 impl_tool!("P_encil");
119 impl_tool!("_Airbrush");
120
121 let drawing_area = gtk::DrawingArea::new();
122 drawing_area.set_size_request(DEFAULT_WIDTH, DEFAULT_HEIGHT);
123 drawing_area.set_hexpand(true);
124 drawing_area.set_events(
125 gdk::EventMask::BUTTON_PRESS_MASK
126 | gdk::EventMask::BUTTON_RELEASE_MASK
127 | gdk::EventMask::POINTER_MOTION_MASK,
128 );
129 let state_weak = Arc::downgrade(&state);
130 drawing_area.connect_configure_event(move |_, event| {
131 let state = state_weak.upgrade().unwrap();
132 let mut state = state.lock().unwrap();
133 state.set_size(event.get_size());
134 true
135 });
136 let state_weak = Arc::downgrade(&state);
137 drawing_area.connect_button_press_event(move |_, event| {
138 if event.get_button() != 1 {
139 return Inhibit(false);
140 }
141
142 let state = state_weak.upgrade().unwrap();
143 let mut state = state.lock().unwrap();
144 let (x, y) = event.get_position();
145 state.press(x, y).unwrap();
146 Inhibit(false)
147 });
148 let state_weak = Arc::downgrade(&state);
149 drawing_area.connect_button_release_event(move |_, event| {
150 if event.get_button() != 1 {
151 return Inhibit(false);
152 }
153
154 let state = state_weak.upgrade().unwrap();
155 let mut state = state.lock().unwrap();
156 let (x, y) = event.get_position();
157 state.release(x, y).unwrap();
158 Inhibit(false)
159 });
160 drawing_area.connect_motion_notify_event(move |_, event| {
161 let mut state = state.lock().unwrap();
162 let (x, y) = event.get_position();
163 state.motion(x, y).unwrap();
164 Inhibit(false)
165 });
166 drawing_area.connect_draw(move |_, ctx| {
167 //println!("drawing {}", drawing_area);
168 ctx.set_source_rgb(1., 0., 0.);
169 ctx.set_operator(cairo::Operator::Screen);
170 ctx.paint();
171 Inhibit(false)
172 });
173
174 hbox.add(&tools_box);
175 hbox.add(&drawing_area);
176
177 window.add(&hbox);
178
179 window.show_all();
180 }
181
182 pub fn main() {
183 let application = gtk::Application::new(
184 Some("fr.linkmauve.TabletEmu"),
185 gio::ApplicationFlags::empty(),
186 )
187 .expect("Initialisation failed…");
188 application.connect_activate(build_ui);
189 application.run(&args().collect::<Vec<_>>());
190 }