The beginnings of a simple library...
This commit is contained in:
@@ -9,12 +9,8 @@ edition = "2021"
|
|||||||
name = "nytegearui"
|
name = "nytegearui"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "demo"
|
|
||||||
path = "src/main.rs"
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
winit = "0.29"
|
winit = "0.29"
|
||||||
glutin = "0.31.0"
|
glutin = "0.31.0"
|
||||||
gl = "0.14"
|
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" }
|
||||||
82
examples/rectangle_widget.rs
Normal file
82
examples/rectangle_widget.rs
Normal file
@@ -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");
|
||||||
|
*/
|
||||||
|
}
|
||||||
6
examples/simple_window.rs
Normal file
6
examples/simple_window.rs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
use nytegearui::window::Window;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut window = Window::new();
|
||||||
|
window.run();
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
use crate::window::Renderer;
|
use gl::types::GLfloat;
|
||||||
use raw_gl_context::{GlConfig, GlContext};
|
use raw_gl_context::{GlConfig, GlContext};
|
||||||
use winit::window::Window as WinitWindow;
|
use winit::window::Window as WinitWindow;
|
||||||
use winit::raw_window_handle::HasRawWindowHandle;
|
|
||||||
|
use crate::widget::color::Color;
|
||||||
|
use crate::window::Renderer;
|
||||||
|
|
||||||
pub struct OpenGLRenderer {
|
pub struct OpenGLRenderer {
|
||||||
context: GlContext,
|
context: GlContext,
|
||||||
@@ -11,14 +13,21 @@ impl OpenGLRenderer
|
|||||||
{
|
{
|
||||||
pub fn try_create(window: &WinitWindow) -> Option<OpenGLRenderer>
|
pub fn try_create(window: &WinitWindow) -> Option<OpenGLRenderer>
|
||||||
{
|
{
|
||||||
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 {
|
gl::load_with(|symbol| context.get_proc_address(symbol) as *const _);
|
||||||
context.make_current();
|
|
||||||
}
|
context
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Some(OpenGLRenderer {
|
Some(OpenGLRenderer {
|
||||||
context,
|
context: gl_context,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -33,7 +42,31 @@ impl Renderer for OpenGLRenderer
|
|||||||
todo!()
|
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!()
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
pub mod window;
|
pub mod window;
|
||||||
pub mod backends;
|
pub mod widget;
|
||||||
|
|
||||||
|
mod backends;
|
||||||
50
src/main.rs
50
src/main.rs
@@ -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");
|
|
||||||
}
|
|
||||||
46
src/widget/color.rs
Normal file
46
src/widget/color.rs
Normal file
@@ -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<u32> 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<u32> 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);
|
||||||
7
src/widget/mod.rs
Normal file
7
src/widget/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
pub use util::*;
|
||||||
|
|
||||||
|
pub mod color;
|
||||||
|
pub mod style;
|
||||||
|
pub mod widget;
|
||||||
|
mod util;
|
||||||
|
|
||||||
16
src/widget/style.rs
Normal file
16
src/widget/style.rs
Normal file
@@ -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),
|
||||||
|
}
|
||||||
|
|
||||||
32
src/widget/util/dimension.rs
Normal file
32
src/widget/util/dimension.rs
Normal file
@@ -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<Dimension> for u32 {
|
||||||
|
fn into(self) -> Dimension {
|
||||||
|
Dimension::Pixel(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<Dimension> 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/widget/util/mod.rs
Normal file
7
src/widget/util/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
mod sizing;
|
||||||
|
mod dimension;
|
||||||
|
mod position;
|
||||||
|
|
||||||
|
pub use dimension::*;
|
||||||
|
pub use sizing::*;
|
||||||
|
pub use position::*;
|
||||||
5
src/widget/util/position.rs
Normal file
5
src/widget/util/position.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#[derive(Default, Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub struct Position {
|
||||||
|
pub x: u32,
|
||||||
|
pub y: u32
|
||||||
|
}
|
||||||
197
src/widget/util/sizing.rs
Normal file
197
src/widget/util/sizing.rs
Normal file
@@ -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<AbsoluteSize> 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<Size>,
|
||||||
|
pub max_size: Option<Size>,
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/widget/widget.rs
Normal file
11
src/widget/widget.rs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
use crate::widget::Position;
|
||||||
|
use crate::widget::style::Style;
|
||||||
|
use crate::window::Renderer;
|
||||||
|
|
||||||
|
trait Widget {
|
||||||
|
fn style(&self) -> Option<Style>;
|
||||||
|
fn position(&self) -> Position;
|
||||||
|
fn draw(&self, renderer: &dyn Renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1,16 +1,23 @@
|
|||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
use winit::{
|
use winit::{
|
||||||
event::{Event, WindowEvent},
|
|
||||||
event_loop::{ControlFlow, EventLoop},
|
event_loop::{ControlFlow, EventLoop},
|
||||||
window::{Window as WinitWindow, WindowBuilder},
|
window::{Window as WinitWindow, WindowBuilder},
|
||||||
};
|
};
|
||||||
|
use winit::event::{Event, WindowEvent};
|
||||||
|
use winit::platform::pump_events::EventLoopExtPumpEvents;
|
||||||
|
|
||||||
use crate::backends::OpenGLRenderer;
|
use crate::backends::OpenGLRenderer;
|
||||||
|
use crate::widget::color::Color;
|
||||||
|
|
||||||
pub trait Renderer
|
pub trait Renderer
|
||||||
{
|
{
|
||||||
fn draw_pixel(&mut self, x: u32, y: u32, value: u32);
|
fn draw_pixel(&mut self, x: u32, y: u32, value: u32);
|
||||||
fn draw_rect(&mut self, x: u32, y: u32, width: u32, height: u32, value: u32);
|
fn draw_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, value: u32);
|
fn fill_rect(&mut self, x: u32, y: u32, width: u32, height: u32, color: Color);
|
||||||
|
fn clear_background(&mut self, background_color: Color);
|
||||||
|
fn begin(&mut self);
|
||||||
|
fn end(&mut self);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Widget
|
pub trait Widget
|
||||||
@@ -49,6 +56,51 @@ impl Window
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn run(&mut self) {
|
||||||
|
let timeout = None;
|
||||||
|
let mut exit_loop = false;
|
||||||
|
let mut event_queue = VecDeque::new();
|
||||||
|
|
||||||
|
while !exit_loop {
|
||||||
|
self.event_loop.pump_events(timeout, |event, elwt| {
|
||||||
|
match event {
|
||||||
|
Event::WindowEvent {
|
||||||
|
event: WindowEvent::CloseRequested,
|
||||||
|
window_id,
|
||||||
|
} if window_id == self.window.id() => {
|
||||||
|
elwt.exit();
|
||||||
|
exit_loop = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => event_queue.push_back(event)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
while let Some(event) = event_queue.pop_front() {
|
||||||
|
match event {
|
||||||
|
Event::AboutToWait => {
|
||||||
|
self.window.request_redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
Event::WindowEvent {
|
||||||
|
event: WindowEvent::RedrawRequested,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
self.redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn redraw(&mut self) {
|
||||||
|
self.renderer.begin();
|
||||||
|
self.renderer.clear_background(Color::new(255, 51, 25, 128));
|
||||||
|
self.renderer.end();
|
||||||
|
}
|
||||||
|
|
||||||
fn resolve_rendering_backend(window: &WinitWindow) -> Option<Box<dyn Renderer>>
|
fn resolve_rendering_backend(window: &WinitWindow) -> Option<Box<dyn Renderer>>
|
||||||
{
|
{
|
||||||
//TODO: Expand this to allow for Metal and DirectX Renderers
|
//TODO: Expand this to allow for Metal and DirectX Renderers
|
||||||
|
|||||||
257
tests/test_sizing.rs
Normal file
257
tests/test_sizing.rs
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
extern crate nytegearui;
|
||||||
|
|
||||||
|
use nytegearui::widget::{AbsoluteSize, Dimension, Margin, Padding, Size, Sizing};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_desired_size_only() {
|
||||||
|
let width = 500;
|
||||||
|
let height = 300;
|
||||||
|
|
||||||
|
let child_size = Sizing {
|
||||||
|
padding: Default::default(),
|
||||||
|
margin: Default::default(),
|
||||||
|
min_size: None,
|
||||||
|
max_size: None,
|
||||||
|
size: Size::from((width, height)),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Preferred Size with Equal Space
|
||||||
|
let available_space = AbsoluteSize::from((width, height));
|
||||||
|
let preferred_sizing = child_size.calculate_sizing(available_space);
|
||||||
|
assert_eq!(preferred_sizing.size.width, Dimension::Pixel(width));
|
||||||
|
assert_eq!(preferred_sizing.size.height, Dimension::Pixel(height));
|
||||||
|
|
||||||
|
// Preferred Size with Greater Space
|
||||||
|
let available_space = AbsoluteSize::from((width + 100, height + 100));
|
||||||
|
let preferred_sizing = child_size.calculate_sizing(available_space);
|
||||||
|
assert_eq!(preferred_sizing.size.width, Dimension::Pixel(width));
|
||||||
|
assert_eq!(preferred_sizing.size.height, Dimension::Pixel(height));
|
||||||
|
|
||||||
|
// Preferred Size with Less Space
|
||||||
|
let available_space = AbsoluteSize::from((width - 100, height - 100));
|
||||||
|
let preferred_sizing = child_size.calculate_sizing(available_space);
|
||||||
|
assert_eq!(preferred_sizing.size.width, Dimension::Pixel(width));
|
||||||
|
assert_eq!(preferred_sizing.size.height, Dimension::Pixel(height));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fixed_desired_size_and_no_margin() {
|
||||||
|
let padding_top = 20;
|
||||||
|
let padding_bottom = 30;
|
||||||
|
let padding_left = 40;
|
||||||
|
let padding_right = 60;
|
||||||
|
let width = 500;
|
||||||
|
let height = 300;
|
||||||
|
|
||||||
|
let child_size = Sizing {
|
||||||
|
padding: Padding {
|
||||||
|
top: padding_top.into(),
|
||||||
|
right: padding_right.into(),
|
||||||
|
left: padding_left.into(),
|
||||||
|
bottom: padding_bottom.into(),
|
||||||
|
},
|
||||||
|
margin: Default::default(),
|
||||||
|
min_size: None,
|
||||||
|
max_size: None,
|
||||||
|
size: Size::from((width, height)),
|
||||||
|
};
|
||||||
|
|
||||||
|
// The Sizing should not change for any test
|
||||||
|
let expected_sizing = Sizing {
|
||||||
|
padding: Padding {
|
||||||
|
top: padding_top.into(),
|
||||||
|
right: padding_right.into(),
|
||||||
|
left: padding_left.into(),
|
||||||
|
bottom: padding_bottom.into(),
|
||||||
|
},
|
||||||
|
margin: Default::default(),
|
||||||
|
min_size: None,
|
||||||
|
max_size: None,
|
||||||
|
size: Size::from((width, height)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let width_with_padding = width + padding_left + padding_right;
|
||||||
|
let height_with_padding = height + padding_top + padding_bottom;
|
||||||
|
|
||||||
|
// Preferred Size with Equal Space (including padding)
|
||||||
|
let available_space = AbsoluteSize::from((width_with_padding, height_with_padding));
|
||||||
|
let preferred_sizing = child_size.calculate_sizing(available_space);
|
||||||
|
assert_eq!(preferred_sizing, expected_sizing);
|
||||||
|
|
||||||
|
// Preferred Size with Equal Space (excluding padding)
|
||||||
|
let available_space = AbsoluteSize::from((width, height));
|
||||||
|
let preferred_sizing = child_size.calculate_sizing(available_space);
|
||||||
|
assert_eq!(preferred_sizing, expected_sizing);
|
||||||
|
|
||||||
|
// Preferred Size with Greater Space
|
||||||
|
let available_space = AbsoluteSize::from((width_with_padding + 100, height_with_padding + 100));
|
||||||
|
let preferred_sizing = child_size.calculate_sizing(available_space);
|
||||||
|
assert_eq!(preferred_sizing, expected_sizing);
|
||||||
|
|
||||||
|
// Preferred Size with Less Space
|
||||||
|
let available_space = AbsoluteSize::from((width - 100, height - 100));
|
||||||
|
let preferred_sizing = child_size.calculate_sizing(available_space);
|
||||||
|
assert_eq!(preferred_sizing, expected_sizing);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fixed_desired_size_and_no_padding() {
|
||||||
|
let margin_top = 20;
|
||||||
|
let margin_bottom = 30;
|
||||||
|
let margin_left = 40;
|
||||||
|
let margin_right = 60;
|
||||||
|
let width = 500;
|
||||||
|
let height = 300;
|
||||||
|
|
||||||
|
let child_size = Sizing {
|
||||||
|
margin: Margin {
|
||||||
|
top: margin_top.into(),
|
||||||
|
right: margin_right.into(),
|
||||||
|
left: margin_left.into(),
|
||||||
|
bottom: margin_bottom.into(),
|
||||||
|
},
|
||||||
|
padding: Default::default(),
|
||||||
|
min_size: None,
|
||||||
|
max_size: None,
|
||||||
|
size: Size::from((width, height)),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sizing should not change at any point
|
||||||
|
let expected_sizing = Sizing {
|
||||||
|
margin: Margin {
|
||||||
|
top: margin_top.into(),
|
||||||
|
right: margin_right.into(),
|
||||||
|
left: margin_left.into(),
|
||||||
|
bottom: margin_bottom.into(),
|
||||||
|
},
|
||||||
|
padding: Default::default(),
|
||||||
|
min_size: None,
|
||||||
|
max_size: None,
|
||||||
|
size: Size::from((width, height)),
|
||||||
|
};
|
||||||
|
let height_with_margin = height + margin_top + margin_bottom;
|
||||||
|
let width_with_margin = width + margin_left + margin_right;
|
||||||
|
|
||||||
|
// Preferred Size with Equal Space (including margin)
|
||||||
|
let available_space = AbsoluteSize::from((width_with_margin, height_with_margin));
|
||||||
|
let preferred_sizing = child_size.calculate_sizing(available_space);
|
||||||
|
assert_eq!(preferred_sizing, expected_sizing);
|
||||||
|
|
||||||
|
// Preferred Size with Equal Space (excluding margin)
|
||||||
|
let available_space = AbsoluteSize::from((width, height));
|
||||||
|
let preferred_sizing = child_size.calculate_sizing(available_space);
|
||||||
|
assert_eq!(preferred_sizing, expected_sizing);
|
||||||
|
|
||||||
|
// Preferred Size with Greater Space
|
||||||
|
let available_space = AbsoluteSize::from((width_with_margin + 100, height_with_margin + 100));
|
||||||
|
let preferred_sizing = child_size.calculate_sizing(available_space);
|
||||||
|
assert_eq!(preferred_sizing, expected_sizing);
|
||||||
|
|
||||||
|
// Preferred Size with Less Space
|
||||||
|
let available_space = AbsoluteSize::from((width - 100, height - 100));
|
||||||
|
let preferred_sizing = child_size.calculate_sizing(available_space);
|
||||||
|
assert_eq!(preferred_sizing, expected_sizing);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fixed_desired_size_with_padding_and_margin() {
|
||||||
|
let margin_top = 20;
|
||||||
|
let margin_bottom = 30;
|
||||||
|
let margin_left = 40;
|
||||||
|
let margin_right = 60;
|
||||||
|
let padding_top = margin_top;
|
||||||
|
let padding_bottom = margin_bottom;
|
||||||
|
let padding_left = margin_left;
|
||||||
|
let padding_right = margin_right;
|
||||||
|
let width = 500;
|
||||||
|
let height = 300;
|
||||||
|
|
||||||
|
let child_size = Sizing {
|
||||||
|
margin: Margin {
|
||||||
|
top: margin_top.into(),
|
||||||
|
right: margin_right.into(),
|
||||||
|
left: margin_left.into(),
|
||||||
|
bottom: margin_bottom.into(),
|
||||||
|
},
|
||||||
|
padding: Padding {
|
||||||
|
top: padding_top.into(),
|
||||||
|
right: padding_right.into(),
|
||||||
|
left: padding_left.into(),
|
||||||
|
bottom: padding_bottom.into(),
|
||||||
|
},
|
||||||
|
min_size: None,
|
||||||
|
max_size: None,
|
||||||
|
size: Size::from((width, height)),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sizing should not change at any point
|
||||||
|
let expected_size = Sizing {
|
||||||
|
margin: Margin {
|
||||||
|
top: margin_top.into(),
|
||||||
|
right: margin_right.into(),
|
||||||
|
left: margin_left.into(),
|
||||||
|
bottom: margin_bottom.into(),
|
||||||
|
},
|
||||||
|
padding: Padding {
|
||||||
|
top: padding_top.into(),
|
||||||
|
right: padding_right.into(),
|
||||||
|
left: padding_left.into(),
|
||||||
|
bottom: padding_bottom.into(),
|
||||||
|
},
|
||||||
|
min_size: None,
|
||||||
|
max_size: None,
|
||||||
|
size: Size::from((width, height)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let total_height = height + margin_top + margin_bottom + padding_top + padding_bottom;
|
||||||
|
let total_width = width + margin_left + margin_right + padding_left + padding_right;
|
||||||
|
|
||||||
|
// Preferred Size with Equal Space (including margin)
|
||||||
|
let available_space = AbsoluteSize::from((total_width, total_height));
|
||||||
|
let preferred_sizing = child_size.calculate_sizing(available_space);
|
||||||
|
assert_eq!(preferred_sizing, expected_size);
|
||||||
|
|
||||||
|
// Preferred Size with Equal Space (excluding margin)
|
||||||
|
let available_space = AbsoluteSize::from((width, height));
|
||||||
|
let preferred_sizing = child_size.calculate_sizing(available_space);
|
||||||
|
assert_eq!(preferred_sizing, expected_size);
|
||||||
|
|
||||||
|
// Preferred Size with Greater Space
|
||||||
|
let available_space = AbsoluteSize::from((total_width + 100, total_height + 100));
|
||||||
|
let preferred_sizing = child_size.calculate_sizing(available_space);
|
||||||
|
assert_eq!(preferred_sizing, expected_size);
|
||||||
|
|
||||||
|
// Preferred Size with Less Space
|
||||||
|
let available_space = AbsoluteSize::from((width - 100, height - 100));
|
||||||
|
let preferred_sizing = child_size.calculate_sizing(available_space);
|
||||||
|
assert_eq!(preferred_sizing, expected_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_percent_desired_size_only() {
|
||||||
|
let width = 0.25;
|
||||||
|
let height = 0.25;
|
||||||
|
let available_width = 511;
|
||||||
|
let available_height = 293;
|
||||||
|
|
||||||
|
let child_size = Sizing {
|
||||||
|
margin: Default::default(),
|
||||||
|
padding: Default::default(),
|
||||||
|
min_size: None,
|
||||||
|
max_size: None,
|
||||||
|
size: Size::from((width, height)),
|
||||||
|
};
|
||||||
|
|
||||||
|
// For the first test
|
||||||
|
let expected_size = Sizing {
|
||||||
|
margin: Default::default(),
|
||||||
|
padding: Default::default(),
|
||||||
|
min_size: None,
|
||||||
|
max_size: None,
|
||||||
|
size: Size::from((available_width / 4, available_height / 4)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let available_space = AbsoluteSize::from((available_width, available_height));
|
||||||
|
let calculated_size = child_size.calculate_sizing(available_space);
|
||||||
|
assert_eq!(calculated_size, expected_size);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user