diff --git a/Cargo.lock b/Cargo.lock index ecaee72..f416fbb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1033,6 +1033,7 @@ dependencies = [ "glam", "glium", "hashbrown", + "log", "nohash-hasher", ] diff --git a/kubi-ui/Cargo.toml b/kubi-ui/Cargo.toml index 49db7a5..2037bac 100644 --- a/kubi-ui/Cargo.toml +++ b/kubi-ui/Cargo.toml @@ -9,6 +9,7 @@ hashbrown = "0.14" nohash-hasher = "0.2" glam = { version = "0.24", features = ["approx"] } glium = { git = "https://github.com/glium/glium", rev = "968fc92378caf", optional = true } +log = "0.4" [features] default = ["backend_glium", "builtin_elements"] diff --git a/kubi-ui/shaders/fragment.frag b/kubi-ui/shaders/fragment.frag new file mode 100644 index 0000000..178c33a --- /dev/null +++ b/kubi-ui/shaders/fragment.frag @@ -0,0 +1,10 @@ +#version 300 es + +precision highp float; +out vec4 out_color; +uniform vec4 color; + +void main() { + if (color.w <= 0.) discard; + out_color = color; +} diff --git a/kubi-ui/shaders/vertex.vert b/kubi-ui/shaders/vertex.vert new file mode 100644 index 0000000..9a1fb4e --- /dev/null +++ b/kubi-ui/shaders/vertex.vert @@ -0,0 +1,8 @@ +#version 300 es + +precision highp float; +in vec2 position; + +void main() { + gl_Position = vec4(position, 0., 1.); +} diff --git a/kubi-ui/src/backend/glium.rs b/kubi-ui/src/backend/glium.rs index e69de29..ce92d1b 100644 --- a/kubi-ui/src/backend/glium.rs +++ b/kubi-ui/src/backend/glium.rs @@ -0,0 +1,72 @@ +use glam::Vec2; +use glium::{ + Surface, DrawParameters, Blend, + Program, VertexBuffer, IndexBuffer, + backend::Facade, + index::PrimitiveType, + implement_vertex, uniform, +}; + +use crate::draw::{UiDrawPlan, UiVertex}; + +const VERTEX_SHADER: &str = include_str!("../../shaders/fragment.frag"); +const FRAGMENT_SHADER: &str = include_str!("../../shaders/vertex.vert"); + +#[derive(Clone, Copy)] +struct Vertex { + position: [f32; 2], + color: [f32; 4], +} + +impl From for Vertex { + fn from(v: UiVertex) -> Self { + Self { + position: v.position.to_array(), + color: v.color.to_array(), + } + } +} + +implement_vertex!(Vertex, position, color); + +pub struct GliumUiRenderer { + pub program: glium::Program, + pub vertex_buffer: glium::VertexBuffer, + pub index_buffer: glium::IndexBuffer, +} + +impl GliumUiRenderer { + pub fn new(facade: &F) -> Self { + log::info!("init glium backend for ui"); + log::debug!("init program"); + let program = Program::from_source(facade, VERTEX_SHADER, FRAGMENT_SHADER, None).unwrap(); + log::debug!("init buffers"); + let vertex_buffer = VertexBuffer::empty_persistent(facade, 1024).unwrap(); + let index_buffer = IndexBuffer::empty_persistent(facade, PrimitiveType::TrianglesList, 1024).unwrap(); + Self { + program, + vertex_buffer, + index_buffer, + } + } + + pub fn draw(&mut self, frame: &mut glium::Frame, resolution: Vec2, plan: &UiDrawPlan) { + self.vertex_buffer.write(&plan.vertices.iter().copied().map(Vertex::from).collect::>()); + self.index_buffer.write(&plan.indices); + + let params = DrawParameters { + blend: Blend::alpha_blending(), + ..Default::default() + }; + + frame.draw( + &self.vertex_buffer, + &self.index_buffer, + &self.program, + &uniform! { + resolution: resolution.to_array(), + }, + ¶ms, + ).unwrap(); + } +} diff --git a/kubi-ui/src/draw.rs b/kubi-ui/src/draw.rs index d183ebe..c03b129 100644 --- a/kubi-ui/src/draw.rs +++ b/kubi-ui/src/draw.rs @@ -1,7 +1,8 @@ -use glam::{Vec2, vec2, Vec4}; +use std::num::NonZeroU16; +use glam::{Vec2, Vec4}; -#[derive(Clone, Copy, Debug)] -pub enum UiDrawCall { +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum UiDrawCommand { ///Filled, colored rectangle Rectangle { ///Position in pixels @@ -13,10 +14,60 @@ pub enum UiDrawCall { } } -pub struct UiDrawCalls { - pub calls: Vec, +#[derive(Default)] +pub struct UiDrawCommands { + pub commands: Vec, } +// impl UiDrawCommands { +// pub fn compare(&self, other: &Self) -> bool { +// // if self.commands.len() != other.commands.len() { return false } +// // self.commands.iter().zip(other.commands.iter()).all(|(a, b)| a == b) +// } +// } + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct UiVertex { + pub position: Vec2, + pub color: Vec4, + //pub texture: Option, +} + +#[derive(Default)] pub struct UiDrawPlan { - + pub vertices: Vec, + pub indices: Vec, +} + +impl UiDrawPlan { + pub fn build(calls: &UiDrawCommands) -> Self { + let mut plan = Self::default(); + for call in &calls.commands { + match call { + UiDrawCommand::Rectangle { position, size, color } => { + let idx = plan.vertices.len() as u32; + plan.indices.extend([idx, idx + 1, idx + 2, idx, idx + 2, idx + 3]); + plan.vertices.extend([ + UiVertex { + position: *position, + color: *color, + }, + UiVertex { + position: *position + Vec2::new(size.x, 0.0), + color: *color, + }, + UiVertex { + position: *position + *size, + color: *color, + }, + UiVertex { + position: *position + Vec2::new(0.0, size.y), + color: *color, + }, + ]); + } + } + } + plan + } } diff --git a/kubi-ui/src/element.rs b/kubi-ui/src/element.rs index 4d8c786..89e880b 100644 --- a/kubi-ui/src/element.rs +++ b/kubi-ui/src/element.rs @@ -1,7 +1,7 @@ use std::any::Any; use crate::{ LayoutInfo, - draw::UiDrawCall, + draw::UiDrawCommand, measure::Response, state::StateRepo }; @@ -17,5 +17,5 @@ pub trait UiElement { fn is_stateless(&self) -> bool { self.state_id().is_none() } fn init_state(&self) -> Option> { None } fn measure(&self, state: &StateRepo, layout: &LayoutInfo) -> Response; - fn draw(&self, measure: &Response, state: &mut StateRepo, layout: &LayoutInfo, draw: &mut Vec); + fn process(&self, measure: &Response, state: &mut StateRepo, layout: &LayoutInfo, draw: &mut Vec); } diff --git a/kubi-ui/src/element/container.rs b/kubi-ui/src/element/container.rs index 4f21d20..9da7dcc 100644 --- a/kubi-ui/src/element/container.rs +++ b/kubi-ui/src/element/container.rs @@ -1,5 +1,5 @@ use glam::{Vec2, Vec4}; -use crate::{UiDirection, LayoutInfo, draw::UiDrawCall, measure::{IsMeasurable, Response}, state::StateRepo, UiSize}; +use crate::{UiDirection, LayoutInfo, draw::UiDrawCommand, measure::{IsMeasurable, Response}, state::StateRepo, UiSize}; use super::UiElement; #[derive(Default, Clone, Copy, Debug)] @@ -73,9 +73,9 @@ impl UiElement for Container { Response { desired_size: size } } - fn draw(&self, measure: &Response, state: &mut StateRepo, layout: &LayoutInfo, draw: &mut Vec) { + fn process(&self, measure: &Response, state: &mut StateRepo, layout: &LayoutInfo, draw: &mut Vec) { if let Some(color) = self.background { - draw.push(UiDrawCall::Rectangle { + draw.push(UiDrawCommand::Rectangle { position: layout.position, size: measure.desired_size, color diff --git a/kubi-ui/src/element/progress_bar.rs b/kubi-ui/src/element/progress_bar.rs index c9879f1..e9ec7de 100644 --- a/kubi-ui/src/element/progress_bar.rs +++ b/kubi-ui/src/element/progress_bar.rs @@ -1,7 +1,7 @@ use glam::{vec2, Vec4}; use crate::{ UiSize, LayoutInfo, - draw::UiDrawCall, + draw::UiDrawCommand, measure::Response, state::StateRepo }; @@ -36,14 +36,14 @@ impl UiElement for ProgressBar { } } - fn draw(&self, measure: &Response, state: &mut StateRepo, layout: &LayoutInfo, draw: &mut Vec) { - draw.push(UiDrawCall::Rectangle { + fn process(&self, measure: &Response, state: &mut StateRepo, layout: &LayoutInfo, draw: &mut Vec) { + draw.push(UiDrawCommand::Rectangle { position: layout.position, size: measure.desired_size, color: self.color_background }); - draw.push(UiDrawCall::Rectangle { + draw.push(UiDrawCommand::Rectangle { position: layout.position, size: measure.desired_size * vec2(self.value, 1.0), color: self.color_foreground diff --git a/kubi-ui/src/element/spacer.rs b/kubi-ui/src/element/spacer.rs index 38f754a..6bd9123 100644 --- a/kubi-ui/src/element/spacer.rs +++ b/kubi-ui/src/element/spacer.rs @@ -1,5 +1,5 @@ use glam::vec2; -use crate::{state::StateRepo, LayoutInfo, measure::Response, draw::UiDrawCall, UiDirection}; +use crate::{state::StateRepo, LayoutInfo, measure::Response, draw::UiDrawCommand, UiDirection}; use super::UiElement; pub struct Spacer(f32); @@ -14,5 +14,5 @@ impl UiElement for Spacer { } } - fn draw(&self, _measure: &Response, _state: &mut StateRepo, _layout: &LayoutInfo, _draw: &mut Vec) {} + fn process(&self, _measure: &Response, _state: &mut StateRepo, _layout: &LayoutInfo, _draw: &mut Vec) {} } diff --git a/kubi-ui/src/lib.rs b/kubi-ui/src/lib.rs index cc229fc..0f45089 100644 --- a/kubi-ui/src/lib.rs +++ b/kubi-ui/src/lib.rs @@ -8,12 +8,19 @@ pub mod backend; pub mod measure; pub mod state; +use element::UiElement; use state::StateRepo; +use event::UiEvent; +use draw::{UiDrawCommands, UiDrawPlan}; pub struct KubiUi { mouse_position: Vec2, stateful_state: StateRepo, - event_queue: VecDeque, + event_queue: VecDeque, + prev_draw_commands: UiDrawCommands, + draw_commands: UiDrawCommands, + draw_plan: UiDrawPlan, + draw_plan_modified: bool, } impl KubiUi { @@ -22,8 +29,31 @@ impl KubiUi { mouse_position: Vec2::ZERO, stateful_state: StateRepo::default(), event_queue: VecDeque::new(), + // root_elements: Vec::new(), + prev_draw_commands: UiDrawCommands::default(), + draw_commands: UiDrawCommands::default(), + draw_plan: UiDrawPlan::default(), + draw_plan_modified: false, } } + + pub fn begin(&mut self) { + std::mem::swap(&mut self.prev_draw_commands, &mut self.draw_commands); + self.draw_plan_modified = false; + self.draw_commands.commands.clear(); + } + + pub fn end(&mut self) { + if self.draw_commands.commands == self.prev_draw_commands.commands { + return + } + self.draw_plan = UiDrawPlan::build(&self.draw_commands); + self.draw_plan_modified = true; + } + + pub fn draw_plan(&self) -> (bool, &UiDrawPlan) { + (self.draw_plan_modified, &self.draw_plan) + } } impl Default for KubiUi { diff --git a/kubi/src/gui_integration.rs b/kubi/src/gui_integration.rs new file mode 100644 index 0000000..e69de29