ranma 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,227 @@
1
+ use magnus::{function, method, prelude::*, Error, Ruby};
2
+
3
+ #[magnus::wrap(class = "Ranma::PhysicalSize", free_immediately, size)]
4
+ #[derive(Clone, Copy, Debug)]
5
+ pub struct RbPhysicalSize {
6
+ pub width: u32,
7
+ pub height: u32,
8
+ }
9
+
10
+ impl RbPhysicalSize {
11
+ pub fn new(width: u32, height: u32) -> Self {
12
+ Self { width, height }
13
+ }
14
+
15
+ pub fn width(&self) -> u32 {
16
+ self.width
17
+ }
18
+
19
+ pub fn height(&self) -> u32 {
20
+ self.height
21
+ }
22
+
23
+ pub fn to_a(&self) -> (u32, u32) {
24
+ (self.width, self.height)
25
+ }
26
+
27
+ pub fn inspect(&self) -> String {
28
+ format!("#<Ranma::PhysicalSize {}x{}>", self.width, self.height)
29
+ }
30
+
31
+ pub fn to_s(&self) -> String {
32
+ format!("{}x{}", self.width, self.height)
33
+ }
34
+ }
35
+
36
+ impl From<tao::dpi::PhysicalSize<u32>> for RbPhysicalSize {
37
+ fn from(size: tao::dpi::PhysicalSize<u32>) -> Self {
38
+ Self {
39
+ width: size.width,
40
+ height: size.height,
41
+ }
42
+ }
43
+ }
44
+
45
+ impl From<RbPhysicalSize> for tao::dpi::PhysicalSize<u32> {
46
+ fn from(size: RbPhysicalSize) -> Self {
47
+ tao::dpi::PhysicalSize::new(size.width, size.height)
48
+ }
49
+ }
50
+
51
+ #[magnus::wrap(class = "Ranma::LogicalSize", free_immediately, size)]
52
+ #[derive(Clone, Copy, Debug)]
53
+ pub struct RbLogicalSize {
54
+ pub width: f64,
55
+ pub height: f64,
56
+ }
57
+
58
+ impl RbLogicalSize {
59
+ pub fn new(width: f64, height: f64) -> Self {
60
+ Self { width, height }
61
+ }
62
+
63
+ pub fn width(&self) -> f64 {
64
+ self.width
65
+ }
66
+
67
+ pub fn height(&self) -> f64 {
68
+ self.height
69
+ }
70
+
71
+ pub fn to_a(&self) -> (f64, f64) {
72
+ (self.width, self.height)
73
+ }
74
+
75
+ pub fn inspect(&self) -> String {
76
+ format!("#<Ranma::LogicalSize {}x{}>", self.width, self.height)
77
+ }
78
+
79
+ pub fn to_s(&self) -> String {
80
+ format!("{}x{}", self.width, self.height)
81
+ }
82
+ }
83
+
84
+ impl From<tao::dpi::LogicalSize<f64>> for RbLogicalSize {
85
+ fn from(size: tao::dpi::LogicalSize<f64>) -> Self {
86
+ Self {
87
+ width: size.width,
88
+ height: size.height,
89
+ }
90
+ }
91
+ }
92
+
93
+ impl From<RbLogicalSize> for tao::dpi::LogicalSize<f64> {
94
+ fn from(size: RbLogicalSize) -> Self {
95
+ tao::dpi::LogicalSize::new(size.width, size.height)
96
+ }
97
+ }
98
+
99
+ #[magnus::wrap(class = "Ranma::PhysicalPosition", free_immediately, size)]
100
+ #[derive(Clone, Copy, Debug)]
101
+ pub struct RbPhysicalPosition {
102
+ pub x: i32,
103
+ pub y: i32,
104
+ }
105
+
106
+ impl RbPhysicalPosition {
107
+ pub fn new(x: i32, y: i32) -> Self {
108
+ Self { x, y }
109
+ }
110
+
111
+ pub fn x(&self) -> i32 {
112
+ self.x
113
+ }
114
+
115
+ pub fn y(&self) -> i32 {
116
+ self.y
117
+ }
118
+
119
+ pub fn to_a(&self) -> (i32, i32) {
120
+ (self.x, self.y)
121
+ }
122
+
123
+ pub fn inspect(&self) -> String {
124
+ format!("#<Ranma::PhysicalPosition ({}, {})>", self.x, self.y)
125
+ }
126
+
127
+ pub fn to_s(&self) -> String {
128
+ format!("({}, {})", self.x, self.y)
129
+ }
130
+ }
131
+
132
+ impl From<tao::dpi::PhysicalPosition<i32>> for RbPhysicalPosition {
133
+ fn from(pos: tao::dpi::PhysicalPosition<i32>) -> Self {
134
+ Self { x: pos.x, y: pos.y }
135
+ }
136
+ }
137
+
138
+ impl From<RbPhysicalPosition> for tao::dpi::PhysicalPosition<i32> {
139
+ fn from(pos: RbPhysicalPosition) -> Self {
140
+ tao::dpi::PhysicalPosition::new(pos.x, pos.y)
141
+ }
142
+ }
143
+
144
+ #[magnus::wrap(class = "Ranma::LogicalPosition", free_immediately, size)]
145
+ #[derive(Clone, Copy, Debug)]
146
+ pub struct RbLogicalPosition {
147
+ pub x: f64,
148
+ pub y: f64,
149
+ }
150
+
151
+ impl RbLogicalPosition {
152
+ pub fn new(x: f64, y: f64) -> Self {
153
+ Self { x, y }
154
+ }
155
+
156
+ pub fn x(&self) -> f64 {
157
+ self.x
158
+ }
159
+
160
+ pub fn y(&self) -> f64 {
161
+ self.y
162
+ }
163
+
164
+ pub fn to_a(&self) -> (f64, f64) {
165
+ (self.x, self.y)
166
+ }
167
+
168
+ pub fn inspect(&self) -> String {
169
+ format!("#<Ranma::LogicalPosition ({}, {})>", self.x, self.y)
170
+ }
171
+
172
+ pub fn to_s(&self) -> String {
173
+ format!("({}, {})", self.x, self.y)
174
+ }
175
+ }
176
+
177
+ impl From<tao::dpi::LogicalPosition<f64>> for RbLogicalPosition {
178
+ fn from(pos: tao::dpi::LogicalPosition<f64>) -> Self {
179
+ Self { x: pos.x, y: pos.y }
180
+ }
181
+ }
182
+
183
+ impl From<RbLogicalPosition> for tao::dpi::LogicalPosition<f64> {
184
+ fn from(pos: RbLogicalPosition) -> Self {
185
+ tao::dpi::LogicalPosition::new(pos.x, pos.y)
186
+ }
187
+ }
188
+
189
+ pub fn define_dpi_classes(ruby: &Ruby, module: &magnus::RModule) -> Result<(), Error> {
190
+ // PhysicalSize
191
+ let physical_size = module.define_class("PhysicalSize", ruby.class_object())?;
192
+ physical_size.define_singleton_method("new", function!(RbPhysicalSize::new, 2))?;
193
+ physical_size.define_method("width", method!(RbPhysicalSize::width, 0))?;
194
+ physical_size.define_method("height", method!(RbPhysicalSize::height, 0))?;
195
+ physical_size.define_method("to_a", method!(RbPhysicalSize::to_a, 0))?;
196
+ physical_size.define_method("inspect", method!(RbPhysicalSize::inspect, 0))?;
197
+ physical_size.define_method("to_s", method!(RbPhysicalSize::to_s, 0))?;
198
+
199
+ // LogicalSize
200
+ let logical_size = module.define_class("LogicalSize", ruby.class_object())?;
201
+ logical_size.define_singleton_method("new", function!(RbLogicalSize::new, 2))?;
202
+ logical_size.define_method("width", method!(RbLogicalSize::width, 0))?;
203
+ logical_size.define_method("height", method!(RbLogicalSize::height, 0))?;
204
+ logical_size.define_method("to_a", method!(RbLogicalSize::to_a, 0))?;
205
+ logical_size.define_method("inspect", method!(RbLogicalSize::inspect, 0))?;
206
+ logical_size.define_method("to_s", method!(RbLogicalSize::to_s, 0))?;
207
+
208
+ // PhysicalPosition
209
+ let physical_position = module.define_class("PhysicalPosition", ruby.class_object())?;
210
+ physical_position.define_singleton_method("new", function!(RbPhysicalPosition::new, 2))?;
211
+ physical_position.define_method("x", method!(RbPhysicalPosition::x, 0))?;
212
+ physical_position.define_method("y", method!(RbPhysicalPosition::y, 0))?;
213
+ physical_position.define_method("to_a", method!(RbPhysicalPosition::to_a, 0))?;
214
+ physical_position.define_method("inspect", method!(RbPhysicalPosition::inspect, 0))?;
215
+ physical_position.define_method("to_s", method!(RbPhysicalPosition::to_s, 0))?;
216
+
217
+ // LogicalPosition
218
+ let logical_position = module.define_class("LogicalPosition", ruby.class_object())?;
219
+ logical_position.define_singleton_method("new", function!(RbLogicalPosition::new, 2))?;
220
+ logical_position.define_method("x", method!(RbLogicalPosition::x, 0))?;
221
+ logical_position.define_method("y", method!(RbLogicalPosition::y, 0))?;
222
+ logical_position.define_method("to_a", method!(RbLogicalPosition::to_a, 0))?;
223
+ logical_position.define_method("inspect", method!(RbLogicalPosition::inspect, 0))?;
224
+ logical_position.define_method("to_s", method!(RbLogicalPosition::to_s, 0))?;
225
+
226
+ Ok(())
227
+ }
@@ -0,0 +1,145 @@
1
+ use std::cell::RefCell;
2
+ use magnus::{function, method, prelude::*, block::Proc, Error, Ruby, RHash, RArray, Symbol, Value, TryConvert, IntoValue};
3
+ use tao::event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget};
4
+ use tao::platform::run_return::EventLoopExtRunReturn;
5
+ use crate::events::convert_event;
6
+ use crate::window::{RbWindow, create_window_from_hash};
7
+ use crate::monitor::RbMonitorHandle;
8
+
9
+ #[magnus::wrap(class = "Ranma::EventLoopTarget", free_immediately, size)]
10
+ pub struct RbEventLoopTarget {
11
+ ptr: RefCell<Option<*const EventLoopWindowTarget<()>>>,
12
+ }
13
+
14
+ unsafe impl Send for RbEventLoopTarget {}
15
+
16
+ impl RbEventLoopTarget {
17
+ pub fn new(target: &EventLoopWindowTarget<()>) -> Self {
18
+ Self {
19
+ ptr: RefCell::new(Some(target as *const _)),
20
+ }
21
+ }
22
+
23
+ pub fn invalidate(&self) {
24
+ *self.ptr.borrow_mut() = None;
25
+ }
26
+
27
+ fn with_target<F, R>(&self, f: F) -> Result<R, Error>
28
+ where
29
+ F: FnOnce(&EventLoopWindowTarget<()>) -> R,
30
+ {
31
+ let borrow = self.ptr.borrow();
32
+ match *borrow {
33
+ Some(ptr) => Ok(f(unsafe { &*ptr })),
34
+ None => Err(Error::new(
35
+ unsafe { Ruby::get_unchecked() }.exception_runtime_error(),
36
+ "EventLoopTarget is only valid inside the Ranma.run callback",
37
+ )),
38
+ }
39
+ }
40
+
41
+ pub fn create_window(&self, opts: Option<RHash>) -> Result<RbWindow, Error> {
42
+ let ruby = unsafe { Ruby::get_unchecked() };
43
+ let borrow = self.ptr.borrow();
44
+ match *borrow {
45
+ Some(ptr) => {
46
+ let target = unsafe { &*ptr };
47
+ create_window_from_hash(&ruby, target, opts)
48
+ }
49
+ None => Err(Error::new(
50
+ ruby.exception_runtime_error(),
51
+ "EventLoopTarget is only valid inside the Ranma.run callback",
52
+ )),
53
+ }
54
+ }
55
+
56
+ pub fn available_monitors(&self) -> Result<RArray, Error> {
57
+ let ruby = unsafe { Ruby::get_unchecked() };
58
+ self.with_target(|t| {
59
+ let arr = ruby.ary_new();
60
+ for m in t.available_monitors() {
61
+ let _ = arr.push(RbMonitorHandle::new(m));
62
+ }
63
+ arr
64
+ })
65
+ }
66
+
67
+ pub fn primary_monitor(&self) -> Result<Option<RbMonitorHandle>, Error> {
68
+ self.with_target(|t| {
69
+ t.primary_monitor().map(|m| RbMonitorHandle::new(m))
70
+ })
71
+ }
72
+ }
73
+
74
+ fn parse_control_flow(value: Value) -> ControlFlow {
75
+ if value.is_nil() {
76
+ return ControlFlow::Wait;
77
+ }
78
+
79
+ if let Ok(sym) = Symbol::try_convert(value) {
80
+ let name: String = sym.funcall("to_s", ()).unwrap_or_default();
81
+ return match name.as_str() {
82
+ "wait" => ControlFlow::Wait,
83
+ "poll" => ControlFlow::Poll,
84
+ "exit" => ControlFlow::Exit,
85
+ _ => ControlFlow::Wait,
86
+ };
87
+ }
88
+
89
+ if let Ok(arr) = <(Symbol, i32)>::try_convert(value) {
90
+ let name: String = arr.0.funcall("to_s", ()).unwrap_or_default();
91
+ if name == "exit" {
92
+ return ControlFlow::ExitWithCode(arr.1);
93
+ }
94
+ }
95
+
96
+ ControlFlow::Wait
97
+ }
98
+
99
+ pub fn ranma_run(callback: Proc) -> Result<i32, Error> {
100
+ let mut event_loop = EventLoop::new();
101
+
102
+ let exit_code = event_loop.run_return(|event, target, control_flow| {
103
+ let ruby = unsafe { Ruby::get_unchecked() };
104
+
105
+ *control_flow = ControlFlow::Wait;
106
+
107
+ let rb_event = match convert_event(&ruby, &event) {
108
+ Some(e) => e,
109
+ None => return,
110
+ };
111
+
112
+ let rb_target = RbEventLoopTarget::new(target);
113
+ let rb_target_value = rb_target.into_value_with(&ruby);
114
+
115
+ let result = callback.call::<_, Value>((rb_event, rb_target_value));
116
+
117
+ // Invalidate the target so it can't be used outside the callback
118
+ if let Ok(target_ref) = <&RbEventLoopTarget>::try_convert(rb_target_value) {
119
+ target_ref.invalidate();
120
+ }
121
+
122
+ match result {
123
+ Ok(val) => {
124
+ *control_flow = parse_control_flow(val);
125
+ }
126
+ Err(e) => {
127
+ eprintln!("Ranma: Error in event callback: {}", e);
128
+ *control_flow = ControlFlow::ExitWithCode(1);
129
+ }
130
+ }
131
+ });
132
+
133
+ Ok(exit_code)
134
+ }
135
+
136
+ pub fn define_event_loop(ruby: &Ruby, module: &magnus::RModule) -> Result<(), Error> {
137
+ module.define_module_function("run", function!(ranma_run, 1))?;
138
+
139
+ let target_class = module.define_class("EventLoopTarget", ruby.class_object())?;
140
+ target_class.define_method("create_window", method!(RbEventLoopTarget::create_window, 1))?;
141
+ target_class.define_method("available_monitors", method!(RbEventLoopTarget::available_monitors, 0))?;
142
+ target_class.define_method("primary_monitor", method!(RbEventLoopTarget::primary_monitor, 0))?;
143
+
144
+ Ok(())
145
+ }
@@ -0,0 +1,305 @@
1
+ use magnus::{Ruby, RHash, Symbol};
2
+ use tao::event::{Event, WindowEvent, StartCause, DeviceEvent, ElementState, MouseButton, TouchPhase, MouseScrollDelta};
3
+ use tao::keyboard::KeyCode;
4
+
5
+ fn sym(ruby: &Ruby, name: &str) -> Symbol {
6
+ ruby.sym_new(name).into()
7
+ }
8
+
9
+ pub fn start_cause_to_symbol(ruby: &Ruby, cause: &StartCause) -> Symbol {
10
+ match cause {
11
+ StartCause::ResumeTimeReached { .. } => sym(ruby, "resume_time_reached"),
12
+ StartCause::WaitCancelled { .. } => sym(ruby, "wait_cancelled"),
13
+ StartCause::Poll => sym(ruby, "poll"),
14
+ StartCause::Init => sym(ruby, "init"),
15
+ _ => sym(ruby, "unknown"),
16
+ }
17
+ }
18
+
19
+ pub fn element_state_to_symbol(ruby: &Ruby, state: &ElementState) -> Symbol {
20
+ match state {
21
+ ElementState::Pressed => sym(ruby, "pressed"),
22
+ ElementState::Released => sym(ruby, "released"),
23
+ _ => sym(ruby, "unknown"),
24
+ }
25
+ }
26
+
27
+ pub fn mouse_button_to_symbol(ruby: &Ruby, button: &MouseButton) -> Symbol {
28
+ match button {
29
+ MouseButton::Left => sym(ruby, "left"),
30
+ MouseButton::Right => sym(ruby, "right"),
31
+ MouseButton::Middle => sym(ruby, "middle"),
32
+ MouseButton::Other(n) => sym(ruby, &format!("other_{}", n)),
33
+ _ => sym(ruby, "unknown"),
34
+ }
35
+ }
36
+
37
+ pub fn key_code_to_symbol(ruby: &Ruby, key: &KeyCode) -> Symbol {
38
+ let name = match key {
39
+ KeyCode::KeyA => "a",
40
+ KeyCode::KeyB => "b",
41
+ KeyCode::KeyC => "c",
42
+ KeyCode::KeyD => "d",
43
+ KeyCode::KeyE => "e",
44
+ KeyCode::KeyF => "f",
45
+ KeyCode::KeyG => "g",
46
+ KeyCode::KeyH => "h",
47
+ KeyCode::KeyI => "i",
48
+ KeyCode::KeyJ => "j",
49
+ KeyCode::KeyK => "k",
50
+ KeyCode::KeyL => "l",
51
+ KeyCode::KeyM => "m",
52
+ KeyCode::KeyN => "n",
53
+ KeyCode::KeyO => "o",
54
+ KeyCode::KeyP => "p",
55
+ KeyCode::KeyQ => "q",
56
+ KeyCode::KeyR => "r",
57
+ KeyCode::KeyS => "s",
58
+ KeyCode::KeyT => "t",
59
+ KeyCode::KeyU => "u",
60
+ KeyCode::KeyV => "v",
61
+ KeyCode::KeyW => "w",
62
+ KeyCode::KeyX => "x",
63
+ KeyCode::KeyY => "y",
64
+ KeyCode::KeyZ => "z",
65
+ KeyCode::Digit0 => "0",
66
+ KeyCode::Digit1 => "1",
67
+ KeyCode::Digit2 => "2",
68
+ KeyCode::Digit3 => "3",
69
+ KeyCode::Digit4 => "4",
70
+ KeyCode::Digit5 => "5",
71
+ KeyCode::Digit6 => "6",
72
+ KeyCode::Digit7 => "7",
73
+ KeyCode::Digit8 => "8",
74
+ KeyCode::Digit9 => "9",
75
+ KeyCode::Escape => "escape",
76
+ KeyCode::F1 => "f1",
77
+ KeyCode::F2 => "f2",
78
+ KeyCode::F3 => "f3",
79
+ KeyCode::F4 => "f4",
80
+ KeyCode::F5 => "f5",
81
+ KeyCode::F6 => "f6",
82
+ KeyCode::F7 => "f7",
83
+ KeyCode::F8 => "f8",
84
+ KeyCode::F9 => "f9",
85
+ KeyCode::F10 => "f10",
86
+ KeyCode::F11 => "f11",
87
+ KeyCode::F12 => "f12",
88
+ KeyCode::Backspace => "backspace",
89
+ KeyCode::Tab => "tab",
90
+ KeyCode::Enter => "enter",
91
+ KeyCode::Space => "space",
92
+ KeyCode::ArrowUp => "up",
93
+ KeyCode::ArrowDown => "down",
94
+ KeyCode::ArrowLeft => "left",
95
+ KeyCode::ArrowRight => "right",
96
+ KeyCode::ShiftLeft => "left_shift",
97
+ KeyCode::ShiftRight => "right_shift",
98
+ KeyCode::ControlLeft => "left_control",
99
+ KeyCode::ControlRight => "right_control",
100
+ KeyCode::AltLeft => "left_alt",
101
+ KeyCode::AltRight => "right_alt",
102
+ KeyCode::SuperLeft => "left_super",
103
+ KeyCode::SuperRight => "right_super",
104
+ KeyCode::Delete => "delete",
105
+ KeyCode::Home => "home",
106
+ KeyCode::End => "end",
107
+ KeyCode::PageUp => "page_up",
108
+ KeyCode::PageDown => "page_down",
109
+ KeyCode::Insert => "insert",
110
+ KeyCode::CapsLock => "caps_lock",
111
+ KeyCode::NumLock => "num_lock",
112
+ KeyCode::ScrollLock => "scroll_lock",
113
+ KeyCode::PrintScreen => "print_screen",
114
+ KeyCode::Pause => "pause",
115
+ KeyCode::Minus => "minus",
116
+ KeyCode::Equal => "equal",
117
+ KeyCode::BracketLeft => "left_bracket",
118
+ KeyCode::BracketRight => "right_bracket",
119
+ KeyCode::Backslash => "backslash",
120
+ KeyCode::Semicolon => "semicolon",
121
+ KeyCode::Quote => "quote",
122
+ KeyCode::Backquote => "backquote",
123
+ KeyCode::Comma => "comma",
124
+ KeyCode::Period => "period",
125
+ KeyCode::Slash => "slash",
126
+ _ => "unknown",
127
+ };
128
+ sym(ruby, name)
129
+ }
130
+
131
+ pub fn convert_window_event<'a>(ruby: &Ruby, event: &WindowEvent<'a>) -> RHash {
132
+ let hash = ruby.hash_new();
133
+ match event {
134
+ WindowEvent::Resized(size) => {
135
+ let _ = hash.aset(sym(ruby, "type"), sym(ruby, "resized"));
136
+ let _ = hash.aset(sym(ruby, "width"), size.width);
137
+ let _ = hash.aset(sym(ruby, "height"), size.height);
138
+ }
139
+ WindowEvent::Moved(pos) => {
140
+ let _ = hash.aset(sym(ruby, "type"), sym(ruby, "moved"));
141
+ let _ = hash.aset(sym(ruby, "x"), pos.x);
142
+ let _ = hash.aset(sym(ruby, "y"), pos.y);
143
+ }
144
+ WindowEvent::CloseRequested => {
145
+ let _ = hash.aset(sym(ruby, "type"), sym(ruby, "close_requested"));
146
+ }
147
+ WindowEvent::Destroyed => {
148
+ let _ = hash.aset(sym(ruby, "type"), sym(ruby, "destroyed"));
149
+ }
150
+ WindowEvent::DroppedFile(path) => {
151
+ let _ = hash.aset(sym(ruby, "type"), sym(ruby, "dropped_file"));
152
+ let _ = hash.aset(sym(ruby, "path"), path.to_string_lossy().to_string());
153
+ }
154
+ WindowEvent::HoveredFile(path) => {
155
+ let _ = hash.aset(sym(ruby, "type"), sym(ruby, "hovered_file"));
156
+ let _ = hash.aset(sym(ruby, "path"), path.to_string_lossy().to_string());
157
+ }
158
+ WindowEvent::HoveredFileCancelled => {
159
+ let _ = hash.aset(sym(ruby, "type"), sym(ruby, "hovered_file_cancelled"));
160
+ }
161
+ WindowEvent::Focused(focused) => {
162
+ let _ = hash.aset(sym(ruby, "type"), sym(ruby, "focused"));
163
+ let _ = hash.aset(sym(ruby, "focused"), *focused);
164
+ }
165
+ WindowEvent::KeyboardInput { event, is_synthetic, .. } => {
166
+ let _ = hash.aset(sym(ruby, "type"), sym(ruby, "keyboard_input"));
167
+ let _ = hash.aset(sym(ruby, "state"), element_state_to_symbol(ruby, &event.state));
168
+ let _ = hash.aset(sym(ruby, "key_code"), key_code_to_symbol(ruby, &event.physical_key));
169
+ let _ = hash.aset(sym(ruby, "is_synthetic"), *is_synthetic);
170
+ }
171
+ WindowEvent::CursorMoved { position, .. } => {
172
+ let _ = hash.aset(sym(ruby, "type"), sym(ruby, "cursor_moved"));
173
+ let _ = hash.aset(sym(ruby, "x"), position.x);
174
+ let _ = hash.aset(sym(ruby, "y"), position.y);
175
+ }
176
+ WindowEvent::CursorEntered { .. } => {
177
+ let _ = hash.aset(sym(ruby, "type"), sym(ruby, "cursor_entered"));
178
+ }
179
+ WindowEvent::CursorLeft { .. } => {
180
+ let _ = hash.aset(sym(ruby, "type"), sym(ruby, "cursor_left"));
181
+ }
182
+ WindowEvent::MouseWheel { delta, phase, .. } => {
183
+ let _ = hash.aset(sym(ruby, "type"), sym(ruby, "mouse_wheel"));
184
+ match delta {
185
+ MouseScrollDelta::LineDelta(x, y) => {
186
+ let _ = hash.aset(sym(ruby, "delta_type"), sym(ruby, "line"));
187
+ let _ = hash.aset(sym(ruby, "delta_x"), *x as f64);
188
+ let _ = hash.aset(sym(ruby, "delta_y"), *y as f64);
189
+ }
190
+ MouseScrollDelta::PixelDelta(pos) => {
191
+ let _ = hash.aset(sym(ruby, "delta_type"), sym(ruby, "pixel"));
192
+ let _ = hash.aset(sym(ruby, "delta_x"), pos.x);
193
+ let _ = hash.aset(sym(ruby, "delta_y"), pos.y);
194
+ }
195
+ _ => {}
196
+ }
197
+ let phase_sym = match phase {
198
+ TouchPhase::Started => "started",
199
+ TouchPhase::Moved => "moved",
200
+ TouchPhase::Ended => "ended",
201
+ TouchPhase::Cancelled => "cancelled",
202
+ _ => "unknown",
203
+ };
204
+ let _ = hash.aset(sym(ruby, "phase"), sym(ruby, phase_sym));
205
+ }
206
+ WindowEvent::MouseInput { state, button, .. } => {
207
+ let _ = hash.aset(sym(ruby, "type"), sym(ruby, "mouse_input"));
208
+ let _ = hash.aset(sym(ruby, "state"), element_state_to_symbol(ruby, state));
209
+ let _ = hash.aset(sym(ruby, "button"), mouse_button_to_symbol(ruby, button));
210
+ }
211
+ WindowEvent::ScaleFactorChanged { scale_factor, new_inner_size } => {
212
+ let _ = hash.aset(sym(ruby, "type"), sym(ruby, "scale_factor_changed"));
213
+ let _ = hash.aset(sym(ruby, "scale_factor"), *scale_factor);
214
+ let _ = hash.aset(sym(ruby, "new_width"), new_inner_size.width);
215
+ let _ = hash.aset(sym(ruby, "new_height"), new_inner_size.height);
216
+ }
217
+ WindowEvent::ModifiersChanged(modifiers) => {
218
+ let _ = hash.aset(sym(ruby, "type"), sym(ruby, "modifiers_changed"));
219
+ let _ = hash.aset(sym(ruby, "shift"), modifiers.shift_key());
220
+ let _ = hash.aset(sym(ruby, "ctrl"), modifiers.control_key());
221
+ let _ = hash.aset(sym(ruby, "alt"), modifiers.alt_key());
222
+ let _ = hash.aset(sym(ruby, "logo"), modifiers.super_key());
223
+ }
224
+ WindowEvent::ThemeChanged(theme) => {
225
+ let _ = hash.aset(sym(ruby, "type"), sym(ruby, "theme_changed"));
226
+ let theme_sym = match theme {
227
+ tao::window::Theme::Light => "light",
228
+ tao::window::Theme::Dark => "dark",
229
+ _ => "unknown",
230
+ };
231
+ let _ = hash.aset(sym(ruby, "theme"), sym(ruby, theme_sym));
232
+ }
233
+ WindowEvent::ReceivedImeText(text) => {
234
+ let _ = hash.aset(sym(ruby, "type"), sym(ruby, "received_ime_text"));
235
+ let _ = hash.aset(sym(ruby, "text"), text.clone());
236
+ }
237
+ WindowEvent::DecorationsClick => {
238
+ let _ = hash.aset(sym(ruby, "type"), sym(ruby, "decorations_click"));
239
+ }
240
+ _ => {
241
+ let _ = hash.aset(sym(ruby, "type"), sym(ruby, "unknown"));
242
+ }
243
+ }
244
+ hash
245
+ }
246
+
247
+ pub fn convert_event<'a>(ruby: &Ruby, event: &Event<'a, ()>) -> Option<RHash> {
248
+ let hash = ruby.hash_new();
249
+ match event {
250
+ Event::NewEvents(cause) => {
251
+ let _ = hash.aset(sym(ruby, "type"), sym(ruby, "new_events"));
252
+ let _ = hash.aset(sym(ruby, "cause"), start_cause_to_symbol(ruby, cause));
253
+ Some(hash)
254
+ }
255
+ Event::WindowEvent { window_id, event, .. } => {
256
+ let _ = hash.aset(sym(ruby, "type"), sym(ruby, "window_event"));
257
+ let _ = hash.aset(sym(ruby, "window_id"), format!("{:?}", window_id));
258
+ let _ = hash.aset(sym(ruby, "event"), convert_window_event(ruby, event));
259
+ Some(hash)
260
+ }
261
+ Event::DeviceEvent { event, .. } => {
262
+ let _ = hash.aset(sym(ruby, "type"), sym(ruby, "device_event"));
263
+ match event {
264
+ DeviceEvent::MouseMotion { delta, .. } => {
265
+ let inner = ruby.hash_new();
266
+ let _ = inner.aset(sym(ruby, "type"), sym(ruby, "mouse_motion"));
267
+ let _ = inner.aset(sym(ruby, "delta_x"), delta.0);
268
+ let _ = inner.aset(sym(ruby, "delta_y"), delta.1);
269
+ let _ = hash.aset(sym(ruby, "event"), inner);
270
+ }
271
+ _ => {
272
+ let inner = ruby.hash_new();
273
+ let _ = inner.aset(sym(ruby, "type"), sym(ruby, "unknown"));
274
+ let _ = hash.aset(sym(ruby, "event"), inner);
275
+ }
276
+ }
277
+ Some(hash)
278
+ }
279
+ Event::MainEventsCleared => {
280
+ let _ = hash.aset(sym(ruby, "type"), sym(ruby, "main_events_cleared"));
281
+ Some(hash)
282
+ }
283
+ Event::RedrawRequested(_window_id) => {
284
+ let _ = hash.aset(sym(ruby, "type"), sym(ruby, "redraw_requested"));
285
+ Some(hash)
286
+ }
287
+ Event::RedrawEventsCleared => {
288
+ let _ = hash.aset(sym(ruby, "type"), sym(ruby, "redraw_events_cleared"));
289
+ Some(hash)
290
+ }
291
+ Event::LoopDestroyed => {
292
+ let _ = hash.aset(sym(ruby, "type"), sym(ruby, "loop_destroyed"));
293
+ Some(hash)
294
+ }
295
+ Event::Suspended => {
296
+ let _ = hash.aset(sym(ruby, "type"), sym(ruby, "suspended"));
297
+ Some(hash)
298
+ }
299
+ Event::Resumed => {
300
+ let _ = hash.aset(sym(ruby, "type"), sym(ruby, "resumed"));
301
+ Some(hash)
302
+ }
303
+ _ => None,
304
+ }
305
+ }