diff --git a/Cargo.toml b/Cargo.toml index b448908..7fb481e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,12 +9,8 @@ edition = "2021" name = "nytegearui" path = "src/lib.rs" -[[bin]] -name = "demo" -path = "src/main.rs" - [dependencies] winit = "0.29" glutin = "0.31.0" gl = "0.14" -raw-gl-context = { git = "https://github.com/Aargonian/raw-gl-context.git", branch = "master" } +raw-gl-context = { git = "https://github.com/Aargonian/raw-gl-context.git", branch = "master" } \ No newline at end of file diff --git a/examples/rectangle_widget.rs b/examples/rectangle_widget.rs new file mode 100644 index 0000000..0e09264 --- /dev/null +++ b/examples/rectangle_widget.rs @@ -0,0 +1,82 @@ +//TODO: Remove this +#![allow(dead_code)] +#![allow(unused_variables)] + +use nytegearui::widget::{ + Position, Sizing, + style::Style, +}; +use nytegearui::window::{Renderer, Widget}; +use nytegearui::window::Window; + +pub struct RectangleWidget { + position: Position, + sizing: Sizing, + style: Style, +} + +impl RectangleWidget { + pub fn new(width: u32, height: u32) -> Self { + Self { + position: Default::default(), + sizing: Sizing { + size: (width, height).into(), + ..Default::default() + }, + style: Default::default(), + } + } +} + +impl Widget for RectangleWidget { + fn draw(&self, renderer: &mut dyn Renderer) { + //renderer.set_style(); + /* + renderer.fill_rect(self.position.x, + self.position.y, + self.sizing.size.width, + self.sizing.size.height, + self.style.foreground_color); + + */ + } +} + +fn main() { + let window = Window::new(); + + let rect_widget = RectangleWidget::new(50, 50); + /* + event_loop.run(move |event, window_target| { + + match event { + Event::WindowEvent { + event: winit::event::WindowEvent::CloseRequested, + .. + } => { + window_target.exit(); + } + Event::WindowEvent { + event: WindowEvent::RedrawRequested, + .. + } => { + unsafe { + context.make_current(); + } + + unsafe { + gl::ClearColor(0.2, 0.1, 0.5, 1.0); + gl::Clear(gl::COLOR_BUFFER_BIT); + } + + context.swap_buffers(); + + unsafe { + context.make_not_current(); + } + } + _ => {} + } + }).expect("TODO: panic message"); + */ +} \ No newline at end of file diff --git a/examples/simple_window.rs b/examples/simple_window.rs new file mode 100644 index 0000000..e7151fd --- /dev/null +++ b/examples/simple_window.rs @@ -0,0 +1,6 @@ +use nytegearui::window::Window; + +fn main() { + let mut window = Window::new(); + window.run(); +} \ No newline at end of file diff --git a/src/backends/opengl/opengl_renderer.rs b/src/backends/opengl/opengl_renderer.rs index da991b9..7b126ce 100644 --- a/src/backends/opengl/opengl_renderer.rs +++ b/src/backends/opengl/opengl_renderer.rs @@ -1,7 +1,9 @@ -use crate::window::Renderer; +use gl::types::GLfloat; use raw_gl_context::{GlConfig, GlContext}; use winit::window::Window as WinitWindow; -use winit::raw_window_handle::HasRawWindowHandle; + +use crate::widget::color::Color; +use crate::window::Renderer; pub struct OpenGLRenderer { context: GlContext, @@ -11,14 +13,21 @@ impl OpenGLRenderer { pub fn try_create(window: &WinitWindow) -> Option { - let context = unsafe { GlContext::create(window, GlConfig::default()).unwrap() }; + let gl_context = unsafe { + let potential_context = GlContext::create(window, GlConfig::default()); + if let Ok(context) = potential_context { + context.make_current(); - unsafe { - context.make_current(); - } + gl::load_with(|symbol| context.get_proc_address(symbol) as *const _); + + context + } else { + return None; + } + }; Some(OpenGLRenderer { - context, + context: gl_context, }) } } @@ -33,7 +42,31 @@ impl Renderer for OpenGLRenderer todo!() } - fn fill_rect(&mut self, x: u32, y: u32, width: u32, height: u32, value: u32) { + fn fill_rect(&mut self, x: u32, y: u32, width: u32, height: u32, color: Color) { todo!() } + + fn clear_background(&mut self, background_color: Color) { + let red: GLfloat = ((background_color.r as f64) / 255.0) as GLfloat; + let green: GLfloat = ((background_color.g as f64) / 255.0) as GLfloat; + let blue: GLfloat = ((background_color.b as f64) / 255.0) as GLfloat; + let alpha: GLfloat = ((background_color.a as f64) / 255.0) as GLfloat; + unsafe { + gl::ClearColor(red, green, blue, alpha); + gl::Clear(gl::COLOR_BUFFER_BIT); + } + } + + fn begin(&mut self) { + unsafe { + self.context.make_current(); + } + } + + fn end(&mut self) { + self.context.swap_buffers(); + unsafe { + self.context.make_not_current(); + } + } } diff --git a/src/lib.rs b/src/lib.rs index d650819..e565d9f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,4 @@ pub mod window; -pub mod backends; +pub mod widget; + +mod backends; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index f67f4f3..0000000 --- a/src/main.rs +++ /dev/null @@ -1,50 +0,0 @@ -use raw_gl_context::{GlConfig, GlContext}; - -use winit::event_loop::EventLoop; -use winit::window::WindowBuilder; -use winit::event::{Event, WindowEvent}; - -fn main() { - let event_loop = EventLoop::new().unwrap(); - let window = WindowBuilder::new().build(&event_loop).unwrap(); - - let context = unsafe { GlContext::create(&window, GlConfig::default()).unwrap() }; - - unsafe { - context.make_current(); - } - - gl::load_with(|symbol| context.get_proc_address(symbol) as *const _); - - event_loop.run(move |event, window_target| { - - match event { - Event::WindowEvent { - event: winit::event::WindowEvent::CloseRequested, - .. - } => { - window_target.exit(); - } - Event::WindowEvent { - event: WindowEvent::RedrawRequested, - .. - } => { - unsafe { - context.make_current(); - } - - unsafe { - gl::ClearColor(0.2, 0.1, 0.5, 1.0); - gl::Clear(gl::COLOR_BUFFER_BIT); - } - - context.swap_buffers(); - - unsafe { - context.make_not_current(); - } - } - _ => {} - } - }).expect("TODO: panic message"); -} \ No newline at end of file diff --git a/src/widget/color.rs b/src/widget/color.rs new file mode 100644 index 0000000..11d95cc --- /dev/null +++ b/src/widget/color.rs @@ -0,0 +1,46 @@ +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +pub struct Color { + pub a: u8, + pub r: u8, + pub g: u8, + pub b: u8, +} + +impl Color { + pub const fn new(a: u8, r: u8, g: u8, b: u8) -> Self { + Self { a, r, g, b } + } +} + +impl From for Color { + fn from(argb: u32) -> Self { + Self { + a: ((argb >> 24) & 0xff) as u8, + r: ((argb >> 16) & 0xff) as u8, + g: ((argb >> 8) & 0xff) as u8, + b: (argb & 0xff) as u8, + } + } +} + +impl Into for Color { + fn into(self) -> u32 { + ((self.a as u32) << 24) + | ((self.r as u32) << 16) + | ((self.g as u32) << 8) + | (self.b as u32) + } +} + +// Constants for common colors +pub const BLACK: Color = Color::new(255, 0, 0, 0); +pub const WHITE: Color = Color::new(255, 255, 255, 255); +pub const RED: Color = Color::new(255, 255, 0, 0); +pub const GREEN: Color = Color::new(255, 0, 255, 0); +pub const BLUE: Color = Color::new(255, 0, 0, 255); +pub const YELLOW: Color = Color::new(255, 255, 255, 0); +pub const ORANGE: Color = Color::new(255, 255, 165, 0); +pub const PURPLE: Color = Color::new(255, 128, 0, 128); +pub const GRAY: Color = Color::new(255, 128, 128, 128); +pub const LIGHT_GRAY: Color = Color::new(255, 192, 192, 192); +pub const DARK_GRAY: Color = Color::new(255, 64, 64, 64); diff --git a/src/widget/mod.rs b/src/widget/mod.rs new file mode 100644 index 0000000..fcdd4f0 --- /dev/null +++ b/src/widget/mod.rs @@ -0,0 +1,7 @@ +pub use util::*; + +pub mod color; +pub mod style; +pub mod widget; +mod util; + diff --git a/src/widget/style.rs b/src/widget/style.rs new file mode 100644 index 0000000..1e2db97 --- /dev/null +++ b/src/widget/style.rs @@ -0,0 +1,16 @@ +use crate::widget::color::Color; + +#[derive(Default)] +pub struct Border { + pub thickness: u32, + pub color: Color, +} + +#[derive(Default)] +pub struct Style { + pub background_color: Color, + pub foreground_color: Color, + pub border: Border, + pub corner_radius: (u32, u32, u32, u32), +} + diff --git a/src/widget/util/dimension.rs b/src/widget/util/dimension.rs new file mode 100644 index 0000000..71a6ede --- /dev/null +++ b/src/widget/util/dimension.rs @@ -0,0 +1,32 @@ +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Dimension { + Percent(f64), + Pixel(u32), +} + +impl Default for Dimension { + fn default() -> Self { + Dimension::Pixel(0) + } +} + +impl Into for u32 { + fn into(self) -> Dimension { + Dimension::Pixel(self) + } +} + +impl Into for f64 { + fn into(self) -> Dimension { + Dimension::Percent(self) + } +} + +impl Dimension { + pub fn calculate_dimension_size(&self, available_space: u32) -> u32 { + match self { + Self::Pixel(value) => *value, + Self::Percent(percent) => ((percent * available_space as f64)/100.0) as u32 + } + } +} diff --git a/src/widget/util/mod.rs b/src/widget/util/mod.rs new file mode 100644 index 0000000..f67f0b9 --- /dev/null +++ b/src/widget/util/mod.rs @@ -0,0 +1,7 @@ +mod sizing; +mod dimension; +mod position; + +pub use dimension::*; +pub use sizing::*; +pub use position::*; diff --git a/src/widget/util/position.rs b/src/widget/util/position.rs new file mode 100644 index 0000000..1572868 --- /dev/null +++ b/src/widget/util/position.rs @@ -0,0 +1,5 @@ +#[derive(Default, Clone, Copy, Debug, PartialEq)] +pub struct Position { + pub x: u32, + pub y: u32 +} \ No newline at end of file diff --git a/src/widget/util/sizing.rs b/src/widget/util/sizing.rs new file mode 100644 index 0000000..683b672 --- /dev/null +++ b/src/widget/util/sizing.rs @@ -0,0 +1,197 @@ +use crate::widget::Dimension; + +#[derive(Default, Clone, Copy, Debug, PartialEq)] +pub struct Size { + pub width: Dimension, + pub height: Dimension, +} + +impl From<(u32, u32)> for Size { + fn from(value: (u32, u32)) -> Size { + Size { + width: Dimension::Pixel(value.0), + height: Dimension::Pixel(value.1), + } + } +} + +impl From<(f64, f64)> for Size { + fn from(value: (f64, f64)) -> Size { + Size { + width: Dimension::Percent(value.0), + height: Dimension::Percent(value.1), + } + } +} + +#[derive(Default, Clone, Copy, Debug, PartialEq)] +pub struct AbsoluteSize { + pub width: u32, + pub height: u32, +} + +impl From for Size { + fn from(value: AbsoluteSize) -> Size { + Size { + width: Dimension::Pixel(value.width), + height: Dimension::Pixel(value.height), + } + } +} + +impl From<(u32, u32)> for AbsoluteSize { + fn from(value: (u32, u32)) -> AbsoluteSize { + AbsoluteSize { + width: value.0, + height: value.1, + } + } +} + +#[derive(Default, Clone, Copy, Debug, PartialEq)] +pub struct Margin { + pub top: Dimension, + pub right: Dimension, + pub bottom: Dimension, + pub left: Dimension, +} + +#[derive(Default, Clone, Copy, Debug, PartialEq)] +pub struct Padding { + pub top: Dimension, + pub right: Dimension, + pub bottom: Dimension, + pub left: Dimension, +} + +/// The Sizing and Positional information for a Widget +/// +/// Use this struct to position your widget within the parent element and provide sizing hints for +/// the layout engine. Positional information may be ignored by the layout engine depending on the +/// layout used by the parent widget, and sizing information will be used for a hint-style system. +#[derive(Default, Clone, Debug, PartialEq)] +pub struct Sizing { + pub margin: Margin, + pub padding: Padding, + pub min_size: Option, + pub max_size: Option, + pub size: Size, +} + +impl Sizing { + /// Calculates the size this widget will take up given an available amount of space. + /// + /// This calculation will use a combination of the margin, padding, and sizing values to + /// determine the final size of the widget. If the available space is less than the combination + /// of margin, padding, and the minimum size of this widget, the return value will be greater + /// than the available space. If the available space is larger than the combination of padding, + /// margin, and the max size of this Sizing, it will be less than the available space. For all + /// other situations, the returned Spacing value will be the calculated margin, padding, and + /// size of this widget sizing within the available space. + /// + /// In cases where the size has (likely incorrectly) been configured to be larger than the max + /// size, or smaller than the min size, the larger or smaller of the two will be used, + /// respectively. + /// + /// The returned size from this function will always have a `None` value for the `min_size` and + /// `max_size` fields. The returned `Sizing` object will have the margin, padding, and inner + /// size value to reserve for this widget. + pub fn calculate_sizing(&self, available_space: AbsoluteSize) -> Sizing { + // Helper function for converting dimensions to u32 + fn calculate_dimension_size(dimension: Dimension, available_space: u32) -> u32 { + match dimension { + Dimension::Pixel(value) => value, + Dimension::Percent(percent) => (available_space as f64 * percent) as u32 + } + } + + let top_pad = calculate_dimension_size(self.padding.top, available_space.height); + let right_pad = calculate_dimension_size(self.padding.right, available_space.width); + let bottom_pad = calculate_dimension_size(self.padding.bottom, available_space.height); + let left_pad = calculate_dimension_size(self.padding.left, available_space.width); + + let top_margin = calculate_dimension_size(self.margin.top, available_space.height); + let right_margin = calculate_dimension_size(self.margin.right, available_space.width); + let bottom_margin = calculate_dimension_size(self.margin.bottom, available_space.height); + let left_margin = calculate_dimension_size(self.margin.left, available_space.width); + + // We use box-model sizing, so min/max are calculated to the border not including padding + let remaining_height = available_space.height - (top_margin + bottom_margin); + let remaining_width = available_space.width - (left_margin + right_margin); + + // Calculate our minimum sizes in case min size is greater than declared size + let possible_min_width = calculate_dimension_size(self.size.width, remaining_width); + let possible_min_height = calculate_dimension_size(self.size.height, remaining_height); + let min_width = if let Some(min_size) = self.min_size { + let declared_min_width = calculate_dimension_size(min_size.width, remaining_width); + declared_min_width.min(possible_min_width) + } else { + possible_min_width + }; + let min_height = if let Some(min_size) = self.min_size { + let declared_min_height = calculate_dimension_size(min_size.height, remaining_height); + declared_min_height.min(possible_min_height) + } else { + possible_min_height + }; + + + // Calculate our maximum sizes in case max size is greater than declared size + let possible_max_width = calculate_dimension_size(self.size.width, remaining_width); + let possible_max_height = calculate_dimension_size(self.size.height, remaining_height); + let max_width = if let Some(max_size) = self.max_size { + let declared_max_width = calculate_dimension_size(max_size.width, remaining_width); + declared_max_width.max(possible_max_width) + } else { + possible_max_width + }; + let max_height = if let Some(max_size) = self.max_size { + let declared_max_height = calculate_dimension_size(max_size.height, remaining_height); + declared_max_height.max(possible_max_height) + } else { + possible_max_height + }; + + // Determine final sizes. + let final_width = if remaining_width < min_width { + min_width + } else if remaining_width > max_width { + max_width + } else { + remaining_width + }; + + let final_height = if remaining_height < min_height { + min_height + } else if remaining_height > max_height { + max_height + } else { + remaining_height + }; + + let size = Size { + width: final_width.into(), + height: final_height.into(), + }; + let padding = Padding { + top: top_pad.into(), + right: right_pad.into(), + bottom: bottom_pad.into(), + left: left_pad.into(), + }; + let margin = Margin { + top: top_margin.into(), + right: right_margin.into(), + bottom: bottom_margin.into(), + left: left_margin.into(), + }; + + Sizing { + min_size: None, + max_size: None, + padding, + margin, + size, + } + } +} diff --git a/src/widget/widget.rs b/src/widget/widget.rs new file mode 100644 index 0000000..472f8ab --- /dev/null +++ b/src/widget/widget.rs @@ -0,0 +1,11 @@ +use crate::widget::Position; +use crate::widget::style::Style; +use crate::window::Renderer; + +trait Widget { + fn style(&self) -> Option