diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/bin/fragment.frag | 7 | ||||
-rw-r--r-- | src/bin/phosphor_test.rs | 89 | ||||
-rw-r--r-- | src/bin/vertex.vert | 9 | ||||
-rw-r--r-- | src/keyboard_input.rs | 19 | ||||
-rw-r--r-- | src/lib.rs | 29 | ||||
-rw-r--r-- | src/press_state.rs | 26 | ||||
-rw-r--r-- | src/program_controller.rs | 13 | ||||
-rw-r--r-- | src/render_hint.rs | 33 | ||||
-rw-r--r-- | src/render_request.rs | 40 | ||||
-rw-r--r-- | src/window.rs | 136 | ||||
-rw-r--r-- | src/window/x11.rs | 66 | ||||
-rw-r--r-- | src/window_controller.rs | 42 | ||||
-rw-r--r-- | src/window_manager.rs | 156 |
13 files changed, 665 insertions, 0 deletions
diff --git a/src/bin/fragment.frag b/src/bin/fragment.frag new file mode 100644 index 0000000..99826f6 --- /dev/null +++ b/src/bin/fragment.frag @@ -0,0 +1,7 @@ +#version 330 +out vec4 FragColor; +in vec3 vertexColor; + +void main() { + FragColor = vec4(vertexColor, 1.0); +} diff --git a/src/bin/phosphor_test.rs b/src/bin/phosphor_test.rs new file mode 100644 index 0000000..40a8558 --- /dev/null +++ b/src/bin/phosphor_test.rs @@ -0,0 +1,89 @@ +#![allow(dead_code)] + +// use asbestos::{Shader, ShaderProgram}; +use buffer::*; +use phosphor::*; +// use raw_gl_context::GlContext; + +fn main() { + let my_program = MyProgram {}; + let mut wm = WindowManager::with_program(my_program); + wm.create_window(MainWindow::new()); + wm.run() +} + +struct MyProgram {} +impl ProgramController for MyProgram {} + +struct MainWindow { + // program: ShaderProgram, +} +impl MainWindow { + pub fn new() -> Box<Self> { + // let vertex_shader = Shader::vertex(include_str!("vertex.vert")); + // let fragment_shader = Shader::fragment(include_str!("fragment.frag")); + // let program = ShaderProgram::from_shaders(&[vertex_shader, fragment_shader]); + // Box::new(Self { program }) + Box::new(Self {}) + } +} +impl WindowController for MainWindow { + fn render(&mut self, buffer: &mut Buffer, _: RenderHint) { + println!("Rendering..."); + buffer.fill(Colour::TEAL); + } + + // fn render_gl(&mut self, _context: &mut GlContext) { + // println!("Rendering GL..."); + // unsafe { + // gl::ClearColor(1.0, 0.0, 1.0, 1.0); + // gl::Clear(gl::COLOR_BUFFER_BIT); + // } + // } +} + +// type Pos = [f32; 2]; +// type Color = [f32; 3]; +// #[repr(C, packed)] +// struct Vertex(Pos, Color); + +// const VERTICES: [Vertex; 3] = [ +// Vertex([-0.5, -0.5], [1.0, 0.0, 0.0]), +// Vertex([0.5, -0.5], [0.0, 1.0, 0.0]), +// Vertex([0.0, 0.5], [0.0, 0.0, 1.0]) +// ]; + +// pub struct Buffer { +// pub id: GLuint, +// target: GLuint, +// } +// impl Buffer { +// pub unsafe fn new(target: GLuint) -> Self { +// let mut id: GLuint = 0; +// gl::GenBuffers(1, &mut id); +// Self { id, target } +// } + +// pub unsafe fn bind(&self) { +// gl::BindBuffer(self.target, self.id); +// } + +// pub unsafe fn set_data<D>(&self, data: &[D], usage: GLuint) { +// self.bind(); +// let (_, data_bytes, _) = data.align_to::<u8>(); +// gl::BufferData( +// self.target, +// data_bytes.len() as GLsizeiptr, +// data_bytes.as_ptr() as *const _, +// usage, +// ); +// } +// } + +// impl Drop for Buffer { +// fn drop(&mut self) { +// unsafe { +// gl::DeleteBuffers(1, [self.id].as_ptr()); +// } +// } +// } diff --git a/src/bin/vertex.vert b/src/bin/vertex.vert new file mode 100644 index 0000000..0fd4f8a --- /dev/null +++ b/src/bin/vertex.vert @@ -0,0 +1,9 @@ +#version 330 +in vec2 point; +in vec3 color; +out vec3 vertexColor; + +void main() { + gl_Point = vec4(point, 0.0, 1.0); + vertexColor = color; +} diff --git a/src/keyboard_input.rs b/src/keyboard_input.rs new file mode 100644 index 0000000..139db7e --- /dev/null +++ b/src/keyboard_input.rs @@ -0,0 +1,19 @@ +use crate::*; +use winit::event::KeyboardInput as WinitKeyboardInput; + +#[derive(Copy, Clone)] +pub struct KeyboardInput { + pub state: PressState, + pub keycode: Option<KeyCode>, + pub scancode: u32, +} + +impl From<WinitKeyboardInput> for KeyboardInput { + fn from(input: WinitKeyboardInput) -> Self { + Self { + state: input.state.into(), + keycode: input.virtual_keycode, + scancode: input.scancode, + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..75aaa1b --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,29 @@ +mod keyboard_input; +mod press_state; +mod program_controller; +mod render_hint; +mod render_request; +mod window; +mod window_controller; +mod window_manager; + +use window::Window; + +pub use keyboard_input::KeyboardInput; +pub use press_state::PressState; +pub use program_controller::{DefaultProgramController, ProgramController}; +pub use render_hint::RenderHint; +pub use render_request::RenderRequest; +pub use window_controller::WindowController; +pub use window_manager::WindowManager; + +pub use buffer::{Buffer, Colour}; +pub use winit::{ + event::ModifiersState, + event::VirtualKeyCode as KeyCode, + window::CursorIcon, +}; + +pub type Point = geometry::Point<i32>; +pub type Dimensions = geometry::Dimensions<u32>; +pub type Rect = geometry::Rect<i32, u32>; diff --git a/src/press_state.rs b/src/press_state.rs new file mode 100644 index 0000000..5136d66 --- /dev/null +++ b/src/press_state.rs @@ -0,0 +1,26 @@ +use winit::event::ElementState; + +/// Denotes whether an event was a press event or a release event. +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum PressState { + Pressed, + Released, +} + +impl PressState { + pub fn is_pressed(&self) -> bool { + *self == Self::Pressed + } + pub fn is_released(&self) -> bool { + *self == Self::Released + } +} + +impl From<ElementState> for PressState { + fn from(value: ElementState) -> Self { + match value { + ElementState::Pressed => PressState::Pressed, + ElementState::Released => PressState::Released, + } + } +} diff --git a/src/program_controller.rs b/src/program_controller.rs new file mode 100644 index 0000000..638e5ce --- /dev/null +++ b/src/program_controller.rs @@ -0,0 +1,13 @@ +use crate::*; + +pub trait ProgramController { + fn initialise(&mut self) {} + fn on_render(&mut self) {} + fn on_mouse_moved(&mut self, _position: Point) {} + + fn on_process(&mut self, _create_window: &mut dyn FnMut(Box<dyn WindowController>)) {} +} + +/// An empty program controller, for when a program has only one window. +pub struct DefaultProgramController {} +impl ProgramController for DefaultProgramController {} diff --git a/src/render_hint.rs b/src/render_hint.rs new file mode 100644 index 0000000..e4d37c3 --- /dev/null +++ b/src/render_hint.rs @@ -0,0 +1,33 @@ +use std::ops::*; + +/// A hint to tell a window controller whether the previous render state is +/// intact and can be updated, or was destroyed and needs to be fully redrawn. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RenderHint { + /// The buffer contents are intact, only updates need to be drawn. + Update, + /// The buffer contents were destroyed, a full redraw is required. + Redraw, +} + +impl RenderHint { + pub fn is_update(&self) -> bool { *self == RenderHint::Update } + pub fn is_redraw(&self) -> bool { *self == RenderHint::Redraw } +} + +impl BitAnd for RenderHint { + type Output = RenderHint; + fn bitand(self, other: RenderHint) -> Self::Output { + if self == RenderHint::Redraw || other == RenderHint::Redraw { + RenderHint::Redraw + } else { + RenderHint::Update + } + } +} + +impl BitAndAssign for RenderHint { + fn bitand_assign(&mut self, other: RenderHint) { + *self = *self & other; + } +} diff --git a/src/render_request.rs b/src/render_request.rs new file mode 100644 index 0000000..f336dfc --- /dev/null +++ b/src/render_request.rs @@ -0,0 +1,40 @@ +use crate::*; +use std::ops::*; + +/// A request to the window manager for a render pass to be run. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RenderRequest { + /// A render is not required. + None, + /// A render is required. + Render(RenderHint), +} + +impl RenderRequest { + pub const NONE: RenderRequest = RenderRequest::None; + pub const UPDATE: RenderRequest = RenderRequest::Render(RenderHint::Update); + pub const REDRAW: RenderRequest = RenderRequest::Render(RenderHint::Redraw); + + pub fn is_none(&self) -> bool { *self == RenderRequest::NONE } + pub fn is_some(&self) -> bool { *self != RenderRequest::None } + pub fn is_update(&self) -> bool { *self == RenderRequest::UPDATE } + pub fn is_redraw(&self) -> bool { *self == RenderRequest::REDRAW } +} + +impl BitAnd for RenderRequest { + type Output = RenderRequest; + fn bitand(self, other: RenderRequest) -> Self::Output { + use RenderRequest::*; + match (self, other) { + (None, req) => req, + (req, None) => req, + (Render(a), Render(b)) => Render(a & b), + } + } +} + +impl BitAndAssign for RenderRequest { + fn bitand_assign(&mut self, other: RenderRequest) { + *self = *self & other; + } +} diff --git a/src/window.rs b/src/window.rs new file mode 100644 index 0000000..45e37c9 --- /dev/null +++ b/src/window.rs @@ -0,0 +1,136 @@ +mod x11; +use crate::*; +use raw_window_handle::{HasRawWindowHandle, RawWindowHandle}; +use winit::dpi::{Size, PhysicalSize}; +use winit::event_loop::EventLoopWindowTarget; +use winit::window::{WindowId, Window as WinitWindow, WindowBuilder as WinitWindowBuilder}; +// use raw_gl_context::{GlConfig, GlContext}; + +pub struct Window { + pub controller: Box<dyn WindowController>, + cursor_position: Option<Point>, + winit_window: WinitWindow, + buffer: Buffer, + dimensions: Dimensions, + /// The most recent render request for this window. + render_hint: RenderHint, + graphics_context: Box<dyn GraphicsContext>, + // gl_context: GlContext, +} + +impl Window { + pub unsafe fn new(event_loop: &EventLoopWindowTarget<()>, controller: Box<dyn WindowController>) -> Self { + let mut builder = WinitWindowBuilder::new(); + builder = builder.with_resizable(controller.resizable()); + builder = builder.with_inner_size({ + let dim = controller.initial_dimensions(); + Size::Physical(PhysicalSize::new(dim.width, dim.height)) + }); + if let Some(dim) = controller.minimum_dimensions() { + let size = Size::Physical(PhysicalSize { width: dim.width, height: dim.height }); + builder = builder.with_min_inner_size(size); + } + if let Some(dim) = controller.maximum_dimensions() { + let size = Size::Physical(PhysicalSize { width: dim.width, height: dim.height }); + builder = builder.with_max_inner_size(size); + } + let winit_window = builder.build(event_loop).unwrap(); + + let graphics_context: Box<dyn GraphicsContext> = match winit_window.raw_window_handle() { + RawWindowHandle::Xlib(xlib_handle) => Box::new(x11::X11GraphicsContext::new(xlib_handle)), + _ => panic!("Unknown window handle type"), + }; + // let gl_context = GlContext::create(&winit_window, GlConfig::default()).unwrap(); + // gl_context.make_current(); + // gl::load_with(|symbol| { + // println!("Loaded '{}'", symbol); + // gl_context.get_proc_address(symbol) as *const _ + // }); + Self { + winit_window, + controller, + graphics_context, + render_hint: RenderHint::Redraw, + // gl_context, + buffer: Buffer::new(Dimensions::ZERO), + dimensions: Dimensions::ZERO, + cursor_position: None, + } + } + + pub fn id(&self) -> WindowId { + self.winit_window.id() + } + + pub fn set_minimum_dimensions(&mut self, dimensions: Option<Dimensions>) { + self.winit_window.set_min_inner_size(dimensions.map(|dim| { + Size::Physical(PhysicalSize { width:dim.width, height:dim.height }) + })) + } + pub fn set_maximum_dimensions(&mut self, dimensions: Option<Dimensions>) { + self.winit_window.set_max_inner_size(dimensions.map(|dim| { + Size::Physical(PhysicalSize { width:dim.width, height:dim.height }) + })) + } + pub fn set_title(&mut self, title: &str) { + self.winit_window.set_title(title); + } + + /// Call to update the frame buffer to the new size of the window. + pub fn resize_buffer(&mut self, dimensions: Dimensions) { + if self.dimensions == dimensions { return } + self.dimensions = dimensions; + self.buffer.resize(dimensions); + self.controller.on_resize(dimensions); + self.render_hint = RenderHint::Redraw; + } + + pub fn set_cursor_icon(&mut self, icon: Option<CursorIcon>) { + match icon { + Some(icon) => self.winit_window.set_cursor_icon(icon), + None => self.winit_window.set_cursor_icon(CursorIcon::Default), + }; + } + + /// Call this after a mouse click so that the cursor-hovering callbacks are + /// rerun. This is useful where a click changes the UI layout and a new + /// element that has an on-hover effect appears beneath the cursor. + pub fn bump_mouse(&mut self) { + if let Some(position) = self.cursor_position { + self.controller.on_mouse_move(position) + } + } + + pub fn move_mouse(&mut self, position: Point) { + if self.cursor_position != Some(position) { + self.cursor_position = Some(position); + self.controller.on_mouse_move(position); + } + } + + pub fn check_render_request(&mut self) { + if let RenderRequest::Render(hint) = self.controller.render_request() { + self.render_hint &= hint; + self.winit_window.request_redraw(); + } + } + + pub fn render(&mut self) { + self.controller.render(&mut self.buffer, self.render_hint); + unsafe { self.graphics_context.blit(&self.buffer); } + // Reset the render_hint back to the lowest variant. + self.render_hint = RenderHint::Update; + } + + // pub fn render_gl(&mut self) { + // self.gl_context.make_current(); + // self.controller.render_gl(&mut self.gl_context); + // self.gl_context.swap_buffers(); + // self.gl_context.make_not_current(); + // } +} + +trait GraphicsContext { + /// Fill the graphics context with the contents of the provided buffer. + unsafe fn blit(&mut self, buffer: &Buffer); +} diff --git a/src/window/x11.rs b/src/window/x11.rs new file mode 100644 index 0000000..d4bcbe4 --- /dev/null +++ b/src/window/x11.rs @@ -0,0 +1,66 @@ +use crate::window::GraphicsContext; +use buffer::Buffer; +use geometry::HasDimensions; +use raw_window_handle::XlibHandle; +use std::os::raw::{c_char, c_uint}; +use x11_dl::xlib::{Display, Visual, Xlib, ZPixmap, GC}; + +pub struct X11GraphicsContext { + handle: XlibHandle, + lib: Xlib, + gc: GC, + visual: *mut Visual, + depth: i32, +} + +impl X11GraphicsContext { + pub unsafe fn new(handle: XlibHandle) -> Self { + let lib = match Xlib::open() { + Ok(lib) => lib, + Err(e) => panic!("{:?}", e), + }; + let screen = (lib.XDefaultScreen)(handle.display as *mut Display); + let gc = (lib.XDefaultGC)(handle.display as *mut Display, screen); + let visual = (lib.XDefaultVisual)(handle.display as *mut Display, screen); + let depth = (lib.XDefaultDepth)(handle.display as *mut Display, screen); + + Self { handle, lib, gc, visual, depth } + } +} + +impl GraphicsContext for X11GraphicsContext { + unsafe fn blit(&mut self, buffer: &Buffer) { + let array = buffer.as_u32_slice(); + let dimensions = buffer.dimensions(); + //create image + let image = (self.lib.XCreateImage)( + self.handle.display as *mut Display, + self.visual, + self.depth as u32, + ZPixmap, + 0, + (array.as_ptr()) as *mut c_char, + dimensions.width as u32, + dimensions.height as u32, + 32, + (dimensions.width * 4) as i32, + ); + + //push image to window + (self.lib.XPutImage)( + self.handle.display as *mut Display, + self.handle.window, + self.gc, + image, + 0, + 0, + 0, + 0, + dimensions.width as c_uint, + dimensions.height as c_uint, + ); + + (*image).data = std::ptr::null_mut(); + (self.lib.XDestroyImage)(image); + } +} diff --git a/src/window_controller.rs b/src/window_controller.rs new file mode 100644 index 0000000..d088b99 --- /dev/null +++ b/src/window_controller.rs @@ -0,0 +1,42 @@ +use crate::*; +// use raw_gl_context::GlContext; + +/// Controls a single window. +pub trait WindowController { + fn title(&self) -> String { String::from("Phosphor") } + fn initial_dimensions(&self) -> Dimensions { Dimensions::new(800,600) } + fn minimum_dimensions(&self) -> Option<Dimensions> { None } + fn maximum_dimensions(&self) -> Option<Dimensions> { None } + fn resizable(&self) -> bool { true } + + fn cursor_icon(&mut self) -> Option<CursorIcon> { None } + fn cursor_visible(&mut self) -> bool { true } + + fn on_resize(&mut self, _dimensions: Dimensions) {} + fn on_move(&mut self, _position: Point) {} + fn on_focus_change(&mut self, _focused: bool) {} + fn on_process(&mut self) {} + + fn on_mouse_enter(&mut self) {} + fn on_mouse_exit(&mut self) {} + fn on_mouse_move(&mut self, _position: Point) {} + + fn on_left_mouse_button(&mut self, _pressed: PressState) {} + fn on_middle_mouse_button(&mut self, _pressed: PressState) {} + fn on_right_mouse_button(&mut self, _pressed: PressState) {} + + fn on_keyboard_input(&mut self, _input: KeyboardInput) {} + fn on_keyboard_modifier_change(&mut self, _modifiers: ModifiersState) {} + fn on_character_received(&mut self, _character: char) {} + fn on_file_hovered(&mut self, _path: std::path::PathBuf) {} + fn on_file_dropped(&mut self, _path: std::path::PathBuf) {} + + fn on_line_scroll_horizontal(&mut self, _delta: f64) {} + fn on_line_scroll_vertical(&mut self, _delta: f64) {} + fn on_pixel_scroll_horizontal(&mut self, _delta: f64) {} + fn on_pixel_scroll_vertical(&mut self, _delta: f64) {} + + fn render_request(&mut self) -> RenderRequest { RenderRequest::None } + fn render(&mut self, _buffer: &mut Buffer, _hint: RenderHint) {} + // fn render_gl(&mut self, _context: &mut GlContext) {} +} diff --git a/src/window_manager.rs b/src/window_manager.rs new file mode 100644 index 0000000..144f1c3 --- /dev/null +++ b/src/window_manager.rs @@ -0,0 +1,156 @@ +use crate::*; +use nosferatu::*; + +use std::collections::HashMap; +use winit::event::{Event, MouseButton, StartCause, WindowEvent, MouseScrollDelta}; +use winit::event_loop::EventLoop; +use winit::window::WindowId; + +/// Controls the entire program. +pub struct WindowManager<P: ProgramController> { + limiter: Option<FrameRateLimiter>, + frame_timer: FrameTimer, + event_loop: EventLoop<()>, + windows: HashMap<WindowId, Window>, + program: P, +} + +impl WindowManager<DefaultProgramController> { + pub fn without_program() -> Self { + Self::with_program(DefaultProgramController {}) + } +} + +impl<P: 'static + ProgramController> WindowManager<P> { + pub fn with_program(program: P) -> Self { + Self { + limiter: None, + frame_timer: FrameTimer::one_second(), + event_loop: EventLoop::new(), + windows: HashMap::new(), + program, + } + } + + pub fn with_frame_limit(mut self, limit: usize) -> Self { + self.limiter = Some(FrameRateLimiter::from_frame_rate(limit)); self + } + + /// Used to create one or more windows before the event loop starts. + pub fn create_window(&mut self, controller: Box<dyn WindowController>) { + let window = unsafe { Window::new(&self.event_loop, controller) }; + self.windows.insert(window.id(), window); + } + + /// Starts the event loop, causing program control to be passed permanently to the window manager. + pub fn run(mut self) -> ! { + self.event_loop.run(move |event, window_target, control_flow| { + control_flow.set_poll(); + + match event { + // Event loop has just initialised (is only ever emitted once) + Event::NewEvents(StartCause::Init) => self.program.initialise(), + + Event::WindowEvent { window_id, event } => { + if let Some(window) = self.windows.get_mut(&window_id) { + use WindowEvent::*; + match event { + Resized(dim) => window.resize_buffer(Dimensions::new(dim.width, dim.height)), + Moved(p) => window.controller.on_move(Point::new(p.x, p.y)), + Focused(state) => window.controller.on_focus_change(state), + + CursorEntered { .. } => window.controller.on_mouse_enter(), + CursorLeft { .. } => window.controller.on_mouse_exit(), + CursorMoved { position, .. } => { + let point = Point::new(position.x as i32, position.y as i32); + window.move_mouse(point); + } + MouseWheel {delta, ..} => { + match delta { + MouseScrollDelta::LineDelta(x, y) => { + let (x, y) = (x as f64, y as f64); + if x != 0.0 {window.controller.on_line_scroll_horizontal(x)} + if y != 0.0 {window.controller.on_line_scroll_vertical(y)} + } + MouseScrollDelta::PixelDelta(point) => { + let (x, y) = (point.x, point.y); + if x != 0.0 {window.controller.on_pixel_scroll_horizontal(x)} + if y != 0.0 {window.controller.on_pixel_scroll_vertical(y)} + } + } + }, + MouseInput { state, button, .. } => { + match button { + MouseButton::Left => window.controller.on_left_mouse_button(state.into()), + MouseButton::Middle => window.controller.on_middle_mouse_button(state.into()), + MouseButton::Right => window.controller.on_right_mouse_button(state.into()), + _ => (), + } + window.bump_mouse(); + + } + + KeyboardInput { input, .. } => window.controller.on_keyboard_input(input.into()), + ModifiersChanged(state) => window.controller.on_keyboard_modifier_change(state), + ReceivedCharacter(c) => window.controller.on_character_received(c), + HoveredFile(path) => window.controller.on_file_hovered(path), + DroppedFile(path) => window.controller.on_file_dropped(path), + + // Tell the window, and let it raise its own event to close itself + // When all windows are closed: control_flow.set_exit_with_code(0); + CloseRequested => todo!("window.controller.on_close_requested()"), + _ => (), + } + } + } + + // Called each loop before any rendering would begin. + Event::MainEventsCleared => { + self.frame_timer.tick(); + // println!("{}", self.frame_timer.frame_rate()); + + self.program.on_process(&mut |controller: Box<dyn WindowController>| { + let window = unsafe { Window::new(window_target, controller) }; + self.windows.insert(window.id(), window); + }); + for window in self.windows.values_mut() { + window.controller.on_process(); + let cursor_icon = window.controller.cursor_icon(); + window.set_cursor_icon(cursor_icon); + // let resizable = window.controller.resizable(); + // window.set_resizable(resizable); + // let cursor_visible = window.controller.cursor_visible(); + // window.set_cursor_visible(cursor_visible); + window.check_render_request(); + let title = window.controller.title(); + window.set_title(&title); + let minimum_dimensions = window.controller.minimum_dimensions(); + window.set_minimum_dimensions(minimum_dimensions); + let maximum_dimensions = window.controller.maximum_dimensions(); + window.set_maximum_dimensions(maximum_dimensions); + } + } + // Called if a render was requested for a window + Event::RedrawRequested(window_id) => { + if let Some(window) = self.windows.get_mut(&window_id) { + window.render(); + } + } + // Called after rendering has completed + Event::RedrawEventsCleared => { + if let Some(limiter) = &mut self.limiter { + limiter.tick(); + } + } + + _other => {} // todo!("{:?}", other), + } + }) + } +} + +impl Default for WindowManager<DefaultProgramController> { + fn default() -> Self { + Self::without_program() + } +} |