diff --git a/Cargo.toml b/Cargo.toml index b4a1298..f667831 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,16 +6,29 @@ authors = [ "Charles Saracco ", "Mirko Covizzi ", "Micah Johnston ", + "Billy Messenger ", ] edition = "2018" +[features] +default = ["gl_renderer"] +gl_renderer = ["gl"] +wgpu_renderer = ["wgpu"] + [dependencies] -gl = "0.14.0" log = "0.4.8" +[dependencies.gl] +gl = "0.14" +optional = true + +[dependencies.wgpu] +wgpu = "0.6" +optional = true + [target.'cfg(target_os="linux")'.dependencies] xcb = { version = "0.9", features = ["thread", "xlib_xcb", "dri2"] } -x11 = { version = "2.3", features = ["xlib", "glx"]} +x11 = { version = "2.18", features = ["xlib", "glx"] } libc = "0.2" [target.'cfg(target_os="windows")'.dependencies] diff --git a/examples/open_window.rs b/examples/open_window.rs index 924a382..ad0f699 100644 --- a/examples/open_window.rs +++ b/examples/open_window.rs @@ -1,3 +1,5 @@ +use baseview::Message; + fn main() { let window_open_options = baseview::WindowOpenOptions { title: "baseview", @@ -6,5 +8,57 @@ fn main() { parent: baseview::Parent::None, }; - baseview::Window::open(window_open_options); + let my_program = MyProgram {}; + + baseview::Window::open(window_open_options, my_program); } + +struct MyProgram { + +} + +impl baseview::Receiver for MyProgram { + fn on_message(&mut self, message: Message) { + match message { + Message::CursorMotion(x, y) => { + println!("Cursor moved, x: {}, y: {}", x, y); + }, + Message::MouseDown(button_id) => { + println!("Mouse down, button id: {:?}", button_id); + }, + Message::MouseUp(button_id) => { + println!("Mouse up, button id: {:?}", button_id); + }, + Message::MouseScroll(mouse_scroll) => { + println!("Mouse scroll, {:?}", mouse_scroll); + }, + Message::MouseClick(mouse_click) => { + println!("Mouse click, {:?}", mouse_click); + } + Message::KeyDown(keycode) => { + println!("Key down, keycode: {}", keycode); + }, + Message::KeyUp(keycode) => { + println!("Key up, keycode: {}", keycode); + }, + Message::CharacterInput(char_code) => { + println!("Character input, char_code: {}", char_code); + }, + Message::WindowResized(window_info) => { + println!("Window resized, {:?}", window_info); + }, + Message::WindowFocus => { + println!("Window focused"); + }, + Message::WindowUnfocus => { + println!("Window unfocused"); + }, + Message::Opened(window_info) => { + println!("Window opened, {:?}", window_info); + }, + Message::WillClose => { + println!("Window will close"); + }, + } + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index d1adb24..510896d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,9 @@ mod macos; #[cfg(target_os = "macos")] pub use macos::*; +mod message; +pub use message::*; + pub enum Parent { None, AsIfParented, @@ -29,3 +32,7 @@ pub struct WindowOpenOptions<'a> { pub parent: Parent, } + +pub trait Receiver { + fn on_message(&mut self, message: Message); +} \ No newline at end of file diff --git a/src/macos/window.rs b/src/macos/window.rs index 89de4e5..214a5a0 100644 --- a/src/macos/window.rs +++ b/src/macos/window.rs @@ -6,12 +6,14 @@ use cocoa::appkit::{ use cocoa::base::{nil, NO}; use cocoa::foundation::{NSAutoreleasePool, NSPoint, NSRect, NSSize, NSString}; -use crate::WindowOpenOptions; +use crate::{WindowOpenOptions, Message, Receiver, MouseButtonID, MouseScroll}; -pub struct Window; +pub struct Window { + receiver: R, +} -impl Window { - pub fn open(options: WindowOpenOptions) -> Self { +impl Window { + pub fn open(options: WindowOpenOptions, receiver: R) -> Self { unsafe { let _pool = NSAutoreleasePool::new(nil); @@ -42,7 +44,17 @@ impl Window { current_app.activateWithOptions_(NSApplicationActivateIgnoringOtherApps); app.run(); - Window + receiver.on_message(Message::Opened( + crate::message::WindowInfo { + width: options.width as u32, + height: options.height as u32, + dpi: None, + } + )); + + Window { + receiver, + } } } } diff --git a/src/message.rs b/src/message.rs new file mode 100644 index 0000000..93561a6 --- /dev/null +++ b/src/message.rs @@ -0,0 +1,54 @@ +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum MouseButtonID { + Left, + Middle, + Right, + Back, + Forward, + Other(u8), +} + +#[derive(Debug, Copy, Clone)] +pub struct MouseScroll { + pub x_delta: f64, + pub y_delta: f64, +} + +#[derive(Debug, Copy, Clone)] +pub enum MouseClickType { + Single, + Double, + Triple, +} + +#[derive(Debug, Copy, Clone)] +pub struct MouseClick { + pub id: MouseButtonID, + pub click_type: MouseClickType, + pub x: i32, + pub y: i32, +} + +#[derive(Debug)] +pub struct WindowInfo { + pub width: u32, + pub height: u32, + pub dpi: Option, +} + +#[derive(Debug)] +pub enum Message { + CursorMotion(i32, i32), // new (x, y) relative to window + MouseDown(MouseButtonID), + MouseUp(MouseButtonID), + MouseScroll(MouseScroll), + MouseClick(MouseClick), + KeyDown(u8), // keycode + KeyUp(u8), // keycode + CharacterInput(u32), // character code + WindowResized(WindowInfo), // new (width, height) + WindowFocus, + WindowUnfocus, + Opened(WindowInfo), + WillClose, +} \ No newline at end of file diff --git a/src/win/window.rs b/src/win/window.rs index def2d70..102c1b6 100644 --- a/src/win/window.rs +++ b/src/win/window.rs @@ -25,6 +25,7 @@ use self::winapi::um::winuser::{ use self::winapi::ctypes::c_void; use crate::Parent::WithParent; use crate::{handle_message, WindowOpenOptions}; +use crate::{Message, Receiver, MouseButtonID, MouseScroll}; use std::sync::{Arc, Mutex}; unsafe fn message_box(title: &str, msg: &str) { @@ -111,24 +112,28 @@ unsafe fn unregister_wnd_class(wnd_class: ATOM) { unsafe fn init_gl_context() {} -pub struct Window { +pub struct Window { pub(crate) hwnd: HWND, hdc: HDC, gl_context: HGLRC, window_class: ATOM, + receiver: R, + scaling: Option, // DPI scale, 96.0 is "default". r: f32, g: f32, b: f32, } -impl Window { - pub fn open(options: WindowOpenOptions) { +impl Window { + pub fn open(options: WindowOpenOptions, receiver: R) { unsafe { let mut window = Window { hwnd: null_mut(), hdc: null_mut(), gl_context: null_mut(), window_class: 0, + receiver, + scaling: None, r: 0.3, g: 0.8, b: 0.3, @@ -236,6 +241,14 @@ impl Window { SetTimer(hwnd, 4242, 13, None); + window.receiver.on_message(Message::Opened( + crate::message::WindowInfo { + width: options.width as u32, + height: options.height as u32, + dpi: window.scaling, + } + )); + // todo: decide what to do with the message pump if parent.is_null() { let mut msg: MSG = std::mem::zeroed(); @@ -252,6 +265,8 @@ impl Window { } pub fn close(&self) { + self.receiver.on_message(Message::WillClose); + // todo: see https://github.com/wrl/rutabaga/blob/f30ff67e157375cafdbafe5fb549f1790443a3a8/src/platform/win/window.c#L402 unsafe { wglMakeCurrent(null_mut(), null_mut()); @@ -270,10 +285,14 @@ impl Window { } pub(crate) fn handle_mouse_motion(&mut self, x: i32, y: i32) { - println!("{}, {}", x, y); let r = (x as f32) / 1000.0; let g = (y as f32) / 1000.0; self.r = r; self.g = g; + + self.receiver.on_message(Message::CursorMotion( + x, + y, + )); } } diff --git a/src/x11/mod.rs b/src/x11/mod.rs index e1dd8e0..02f8ba8 100644 --- a/src/x11/mod.rs +++ b/src/x11/mod.rs @@ -1,7 +1,8 @@ -mod window; -pub use window::*; - mod xcb_connection; use xcb_connection::XcbConnection; -mod opengl_util; +mod window; +pub use window::*; + +#[cfg(all(feature = "gl_renderer", not(feature = "wgpu_renderer")))] +mod opengl_util; \ No newline at end of file diff --git a/src/x11/opengl_util.rs b/src/x11/opengl_util.rs index 16763a8..3030dba 100644 --- a/src/x11/opengl_util.rs +++ b/src/x11/opengl_util.rs @@ -1,10 +1,114 @@ -use std::ffi::CString; +use std::ffi::{CString, CStr}; use std::os::raw::{c_int, c_void}; +use std::ptr::null_mut; use ::x11::{glx, xlib}; use super::XcbConnection; +pub fn fb_config(xcb_connection: &XcbConnection) -> *mut glx::__GLXFBConfigRec { + // Check GLX version (>= 1.3 needed) + check_glx_version(&xcb_connection); + + // Get GLX framebuffer config (requires GLX >= 1.3) + #[rustfmt::skip] + let fb_config = get_glxfbconfig( + &xcb_connection, + &[ + glx::GLX_X_RENDERABLE, 1, + glx::GLX_DRAWABLE_TYPE, glx::GLX_WINDOW_BIT, + glx::GLX_RENDER_TYPE, glx::GLX_RGBA_BIT, + glx::GLX_X_VISUAL_TYPE, glx::GLX_TRUE_COLOR, + glx::GLX_RED_SIZE, 8, + glx::GLX_GREEN_SIZE, 8, + glx::GLX_BLUE_SIZE, 8, + glx::GLX_ALPHA_SIZE, 8, + glx::GLX_DEPTH_SIZE, 24, + glx::GLX_STENCIL_SIZE, 8, + glx::GLX_DOUBLEBUFFER, 1, + 0 + ], + ); + + fb_config +} + +pub fn x_visual_info( + xcb_connection: &XcbConnection, + fb_config: *mut glx::__GLXFBConfigRec +) -> *const xlib::XVisualInfo { + // The GLX framebuffer config holds an XVisualInfo, which we'll need for other X operations. + + unsafe { glx::glXGetVisualFromFBConfig( + xcb_connection.conn.get_raw_dpy(), + fb_config + )} +} + +pub fn glx_context( + xcb_connection: &XcbConnection, + fb_config: *mut glx::__GLXFBConfigRec +) -> *mut glx::__GLXcontextRec { + // Load GLX extensions + // We need at least `GLX_ARB_create_context` + let glx_extensions = unsafe { + CStr::from_ptr(glx::glXQueryExtensionsString( + xcb_connection.conn.get_raw_dpy(), + xcb_connection.xlib_display, + )) + .to_str() + .unwrap() + }; + glx_extensions + .find("GLX_ARB_create_context") + .expect("could not find GLX extension GLX_ARB_create_context"); + + // With GLX, we don't need a context pre-created in order to load symbols. + // Otherwise, we would need to create a temporary legacy (dummy) GL context to load them. + // (something that has at least GlXCreateContextAttribsARB) + let glx_create_context_attribs: GlXCreateContextAttribsARBProc = + unsafe { std::mem::transmute(load_gl_func("glXCreateContextAttribsARB")) }; + + // Load all other symbols + unsafe { + gl::load_with(|n| load_gl_func(&n)); + } + + // Check GL3 support + if !gl::GenVertexArrays::is_loaded() { + panic!("no GL3 support available!"); + } + + // Create GLX context attributes. (?) + let context_attribs: [c_int; 5] = [ + glx::arb::GLX_CONTEXT_MAJOR_VERSION_ARB as c_int, + 3, + glx::arb::GLX_CONTEXT_MINOR_VERSION_ARB as c_int, + 0, + 0, + ]; + let ctx = unsafe { + glx_create_context_attribs( + xcb_connection.conn.get_raw_dpy(), + fb_config, + null_mut(), + xlib::True, + &context_attribs[0] as *const c_int, + ) + }; + + if ctx.is_null() + /* || ctx_error_occurred */ + { + panic!("Error when creating a GL 3.0 context"); + } + if unsafe { glx::glXIsDirect(xcb_connection.conn.get_raw_dpy(), ctx) } == 0 { + panic!("Obtained indirect rendering context"); + } + + ctx +} + pub type GlXCreateContextAttribsARBProc = unsafe extern "C" fn( dpy: *mut xlib::Display, fbc: glx::GLXFBConfig, @@ -15,7 +119,7 @@ pub type GlXCreateContextAttribsARBProc = unsafe extern "C" fn( // Check to make sure this system supports the correct version of GLX (>= 1.3 for now) // For now it just panics if not, but TODO: do correct error handling -pub fn check_glx_version(xcb_connection: &XcbConnection) { +fn check_glx_version(xcb_connection: &XcbConnection) { let raw_display = xcb_connection.conn.get_raw_dpy(); let mut maj: c_int = 0; let mut min: c_int = 0; @@ -32,7 +136,7 @@ pub fn check_glx_version(xcb_connection: &XcbConnection) { // Get GLX framebuffer config // History: https://stackoverflow.com/questions/51558473/whats-the-difference-between-a-glx-visual-and-a-fbconfig -pub fn get_glxfbconfig(xcb_connection: &XcbConnection, visual_attribs: &[i32]) -> glx::GLXFBConfig { +fn get_glxfbconfig(xcb_connection: &XcbConnection, visual_attribs: &[i32]) -> glx::GLXFBConfig { let raw_display = xcb_connection.conn.get_raw_dpy(); let xlib_display = xcb_connection.xlib_display; @@ -64,3 +168,18 @@ pub unsafe fn load_gl_func(name: &str) -> *mut c_void { } ptr } + +pub fn xcb_expose( + window_id: u32, + raw_display: *mut xlib::_XDisplay, + ctx: *mut glx::__GLXcontextRec, +) { + unsafe { + glx::glXMakeCurrent(raw_display, window_id as xlib::XID, ctx); + gl::ClearColor(0.3, 0.8, 0.3, 1.0); + gl::Clear(gl::COLOR_BUFFER_BIT); + gl::Flush(); + glx::glXSwapBuffers(raw_display, window_id as xlib::XID); + glx::glXMakeCurrent(raw_display, 0, null_mut()); + } +} \ No newline at end of file diff --git a/src/x11/window.rs b/src/x11/window.rs index a83eea6..349e9e0 100644 --- a/src/x11/window.rs +++ b/src/x11/window.rs @@ -9,21 +9,26 @@ use std::ffi::CStr; use std::os::raw::{c_int, c_void}; use std::ptr::null_mut; -use ::x11::{glx, xlib}; +use ::x11::xlib; // use xcb::dri2; // needed later +#[cfg(all(not(feature = "wgpu_renderer"), feature = "gl_renderer"))] use super::opengl_util; -use super::XcbConnection; -use crate::Parent; -use crate::WindowOpenOptions; -pub struct Window { +use super::XcbConnection; +use crate::{Parent, WindowOpenOptions, Message, Receiver, MouseButtonID, MouseScroll}; + +pub struct Window { xcb_connection: XcbConnection, scaling: Option, // DPI scale, 96.0 is "default". + receiver: R, + + #[cfg(all(not(feature = "wgpu_renderer"), feature = "gl_renderer"))] + ctx: *mut x11::glx::__GLXcontextRec, } -impl Window { - pub fn open(options: WindowOpenOptions) -> Self { +impl Window { + pub fn open(options: WindowOpenOptions, receiver: R) -> Self { // Convert the parent to a X11 window ID if we're given one let parent = match options.parent { Parent::None => None, @@ -34,32 +39,12 @@ impl Window { // Connect to the X server let xcb_connection = XcbConnection::new(); - // Check GLX version (>= 1.3 needed) - opengl_util::check_glx_version(&xcb_connection); - - // Get GLX framebuffer config (requires GLX >= 1.3) - #[rustfmt::skip] - let fb_config = opengl_util::get_glxfbconfig( - &xcb_connection, - &[ - glx::GLX_X_RENDERABLE, 1, - glx::GLX_DRAWABLE_TYPE, glx::GLX_WINDOW_BIT, - glx::GLX_RENDER_TYPE, glx::GLX_RGBA_BIT, - glx::GLX_X_VISUAL_TYPE, glx::GLX_TRUE_COLOR, - glx::GLX_RED_SIZE, 8, - glx::GLX_GREEN_SIZE, 8, - glx::GLX_BLUE_SIZE, 8, - glx::GLX_ALPHA_SIZE, 8, - glx::GLX_DEPTH_SIZE, 24, - glx::GLX_STENCIL_SIZE, 8, - glx::GLX_DOUBLEBUFFER, 1, - 0 - ], - ); - - // The GLX framebuffer config holds an XVisualInfo, which we'll need for other X operations. - let x_visual_info: *const xlib::XVisualInfo = - unsafe { glx::glXGetVisualFromFBConfig(xcb_connection.conn.get_raw_dpy(), fb_config) }; + #[cfg(all(feature = "gl_renderer", not(feature = "wgpu_renderer")))] + let fb_config = opengl_util::fb_config(&xcb_connection); + #[cfg(all(feature = "gl_renderer", not(feature = "wgpu_renderer")))] + let x_visual_info: *const xlib::XVisualInfo = { + opengl_util::x_visual_info(&xcb_connection, fb_config) + }; // Load up DRI2 extensions. // See also: https://www.x.org/releases/X11R7.7/doc/dri2proto/dri2proto.txt @@ -157,36 +142,6 @@ impl Window { title.as_bytes(), ); - // Load GLX extensions - // We need at least `GLX_ARB_create_context` - let glx_extensions = unsafe { - CStr::from_ptr(glx::glXQueryExtensionsString( - xcb_connection.conn.get_raw_dpy(), - xcb_connection.xlib_display, - )) - .to_str() - .unwrap() - }; - glx_extensions - .find("GLX_ARB_create_context") - .expect("could not find GLX extension GLX_ARB_create_context"); - - // With GLX, we don't need a context pre-created in order to load symbols. - // Otherwise, we would need to create a temporary legacy (dummy) GL context to load them. - // (something that has at least GlXCreateContextAttribsARB) - let glx_create_context_attribs: opengl_util::GlXCreateContextAttribsARBProc = - unsafe { std::mem::transmute(opengl_util::load_gl_func("glXCreateContextAttribsARB")) }; - - // Load all other symbols - unsafe { - gl::load_with(|n| opengl_util::load_gl_func(&n)); - } - - // Check GL3 support - if !gl::GenVertexArrays::is_loaded() { - panic!("no GL3 support available!"); - } - // TODO: This requires a global, which is a no. Figure out if there's a better way to do it. /* // installing an event handler to check if error is generated @@ -198,32 +153,8 @@ impl Window { }; */ - // Create GLX context attributes. (?) - let context_attribs: [c_int; 5] = [ - glx::arb::GLX_CONTEXT_MAJOR_VERSION_ARB as c_int, - 3, - glx::arb::GLX_CONTEXT_MINOR_VERSION_ARB as c_int, - 0, - 0, - ]; - let ctx = unsafe { - glx_create_context_attribs( - xcb_connection.conn.get_raw_dpy(), - fb_config, - null_mut(), - xlib::True, - &context_attribs[0] as *const c_int, - ) - }; - - if ctx.is_null() - /* || ctx_error_occurred */ - { - panic!("Error when creating a GL 3.0 context"); - } - if unsafe { glx::glXIsDirect(xcb_connection.conn.get_raw_dpy(), ctx) } == 0 { - panic!("Obtained indirect rendering context"); - } + #[cfg(all(feature = "gl_renderer", not(feature = "wgpu_renderer")))] + let ctx = opengl_util::glx_context(&xcb_connection, fb_config); // Display the window xcb::map_window(&xcb_connection.conn, window_id); @@ -232,22 +163,36 @@ impl Window { xlib::XSync(xcb_connection.conn.get_raw_dpy(), xlib::False); } + #[cfg(all(feature = "gl_renderer", not(feature = "wgpu_renderer")))] let mut x11_window = Self { xcb_connection, scaling: None, + ctx, + receiver, }; x11_window.scaling = x11_window .get_scaling_xft() .or(x11_window.get_scaling_screen_dimensions()); println!("Scale factor: {:?}", x11_window.scaling); - x11_window.handle_events(window_id, ctx); + + x11_window.receiver.on_message(Message::Opened( + crate::message::WindowInfo { + width: options.width as u32, + height: options.height as u32, + dpi: x11_window.scaling, + } + )); + + x11_window.handle_events(window_id); + + x11_window.receiver.on_message(Message::WillClose); return x11_window; } // Event loop - fn handle_events(&self, window_id: u32, ctx: *mut x11::glx::__GLXcontextRec) { + fn handle_events(&mut self, window_id: u32) { let raw_display = self.xcb_connection.conn.get_raw_dpy(); loop { let ev = self.xcb_connection.conn.wait_for_event(); @@ -275,65 +220,68 @@ impl Window { // http://rtbo.github.io/rust-xcb/src/xcb/ffi/xproto.rs.html#445 match event_type { - xcb::EXPOSE => unsafe { - glx::glXMakeCurrent(raw_display, window_id as xlib::XID, ctx); - gl::ClearColor(0.3, 0.8, 0.3, 1.0); - gl::Clear(gl::COLOR_BUFFER_BIT); - gl::Flush(); - glx::glXSwapBuffers(raw_display, window_id as xlib::XID); - glx::glXMakeCurrent(raw_display, 0, null_mut()); + xcb::EXPOSE => { + #[cfg(all(feature = "gl_renderer", not(feature = "wgpu_renderer")))] + opengl_util::xcb_expose(window_id, raw_display, self.ctx); }, xcb::MOTION_NOTIFY => { let event = unsafe { xcb::cast_event::(&event) }; - let x = event.event_x(); - let y = event.event_y(); let detail = event.detail(); - let state = event.state(); - println!("Mouse motion: ({}, {}) -- {} / {}", x, y, detail, state); + + if detail != 4 && detail != 5 { + self.receiver.on_message(Message::CursorMotion( + event.event_x() as i32, + event.event_y() as i32, + )); + } } xcb::BUTTON_PRESS => { let event = unsafe { xcb::cast_event::(&event) }; - let x = event.event_x(); - let y = event.event_y(); let detail = event.detail(); - let state = event.state(); - println!( - "Mouse button pressed: ({}, {}) -- {} / {}", - x, y, detail, state - ); + + match detail { + 4 => { + self.receiver.on_message(Message::MouseScroll( + MouseScroll { + x_delta: 0.0, + y_delta: 1.0, + } + )); + }, + 5 => { + self.receiver.on_message(Message::MouseScroll( + MouseScroll { + x_delta: 0.0, + y_delta: -1.0, + } + )); + } + detail => { + let button_id = mouse_id(detail); + self.receiver.on_message(Message::MouseDown(button_id)); + } + } } xcb::BUTTON_RELEASE => { - let event = unsafe { xcb::cast_event::(&event) }; - let x = event.event_x(); - let y = event.event_y(); + let event = unsafe { xcb::cast_event::(&event) }; let detail = event.detail(); - let state = event.state(); - println!( - "Mouse button released: ({}, {}) -- {} / {}", - x, y, detail, state - ); + + if detail != 4 && detail != 5 { + let button_id = mouse_id(detail); + self.receiver.on_message(Message::MouseUp(button_id)); + } } xcb::KEY_PRESS => { let event = unsafe { xcb::cast_event::(&event) }; - let x = event.event_x(); - let y = event.event_y(); let detail = event.detail(); - let state = event.state(); - println!( - "Keyboard key pressed: ({}, {}) -- {} / {}", - x, y, detail, state - ); + + self.receiver.on_message(Message::KeyDown(detail)); } xcb::KEY_RELEASE => { let event = unsafe { xcb::cast_event::(&event) }; - let x = event.event_x(); - let y = event.event_y(); let detail = event.detail(); - let state = event.state(); - println!( - "Keyboard key released: ({}, {}) -- {} / {}", - x, y, detail, state - ); + + self.receiver.on_message(Message::KeyUp(detail)); } _ => { println!("Unhandled event type: {:?}", event_type); @@ -422,3 +370,14 @@ impl Window { Some(yres) } } + +fn mouse_id(id: u8) -> MouseButtonID { + match id { + 1 => MouseButtonID::Left, + 2 => MouseButtonID::Middle, + 3 => MouseButtonID::Right, + 6 => MouseButtonID::Back, + 7 => MouseButtonID::Forward, + id => MouseButtonID::Other(id), + } +} \ No newline at end of file