mirror of
https://github.com/Astatin3/IntroToWebAuthoring.git
synced 2026-06-09 00:28:00 -06:00
Work on constraint layout
This commit is contained in:
+3
-1
@@ -1,5 +1,7 @@
|
|||||||
mod cursors;
|
mod cursors;
|
||||||
|
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
pub use cursors::{Cursor, set_cursor};
|
pub use cursors::{Cursor, set_cursor};
|
||||||
|
|
||||||
use wasm_bindgen::prelude::wasm_bindgen;
|
use wasm_bindgen::prelude::wasm_bindgen;
|
||||||
@@ -68,7 +70,7 @@ impl App {
|
|||||||
|
|
||||||
// App events
|
// App events
|
||||||
impl App {
|
impl App {
|
||||||
pub fn resize(&mut self, width: u32, height: u32) {
|
pub fn resize(&mut self, width: usize, height: usize) {
|
||||||
self.renderer.resize(width, height);
|
self.renderer.resize(width, height);
|
||||||
self.draw();
|
self.draw();
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -28,7 +28,7 @@ macro_rules! log {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn init(canvas: &web_sys::HtmlCanvasElement, width: u32, height: u32) -> App {
|
pub fn init(canvas: &web_sys::HtmlCanvasElement, width: usize, height: usize) -> App {
|
||||||
log!("WASM Successfully initialized!");
|
log!("WASM Successfully initialized!");
|
||||||
|
|
||||||
let mut renderer = Renderer::new(canvas, width, height);
|
let mut renderer = Renderer::new(canvas, width, height);
|
||||||
@@ -38,7 +38,7 @@ pub fn init(canvas: &web_sys::HtmlCanvasElement, width: u32, height: u32) -> App
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn resize(app: &mut App, width: u32, height: u32) {
|
pub fn resize(app: &mut App, width: usize, height: usize) {
|
||||||
app.resize(width, height);
|
app.resize(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+22
-16
@@ -1,5 +1,5 @@
|
|||||||
// use crate::console_log;
|
// use crate::console_log;
|
||||||
use crate::{log, render::rand};
|
use crate::render::rand;
|
||||||
|
|
||||||
// macro_rules! log {
|
// macro_rules! log {
|
||||||
// ($($t:tt)*) => (console_log(&format_args!($($t)*).to_string()))
|
// ($($t:tt)*) => (console_log(&format_args!($($t)*).to_string()))
|
||||||
@@ -7,14 +7,14 @@ use crate::{log, render::rand};
|
|||||||
|
|
||||||
pub struct ImgBuffer {
|
pub struct ImgBuffer {
|
||||||
pub data: Vec<u8>,
|
pub data: Vec<u8>,
|
||||||
width: u32,
|
width: usize,
|
||||||
height: u32,
|
height: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImgBuffer {
|
impl ImgBuffer {
|
||||||
pub fn new(width: u32, height: u32) -> Self {
|
pub fn new(width: usize, height: usize) -> Self {
|
||||||
ImgBuffer {
|
ImgBuffer {
|
||||||
data: vec![127; (width * height * 4) as usize],
|
data: vec![127; width * height * 4],
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
}
|
}
|
||||||
@@ -30,29 +30,35 @@ impl ImgBuffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn overlay_bitmap(&mut self, other: &Bitmap, xoffset: usize, yoffset: usize) {
|
pub fn overlay_bitmap(&mut self, other: &Bitmap, xoffset: usize, yoffset: usize) {
|
||||||
|
let length = self.data.len();
|
||||||
for y in 0..other.height {
|
for y in 0..other.height {
|
||||||
for x in 0..other.width {
|
for x in 0..other.width {
|
||||||
let offset = (y + yoffset) * self.width as usize + (x + xoffset);
|
// Calculate the offset for the current pixel in the buffer
|
||||||
|
// This uses bitshift instead of multiplication x4 for performance
|
||||||
|
let offset = ((y + yoffset) * self.width + (x + xoffset)) << 2;
|
||||||
|
if offset >= length {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let color = other.data[y * other.width + x];
|
let color = other.data[y * other.width + x];
|
||||||
self.data[offset * 4] = color;
|
self.data[offset] = color;
|
||||||
self.data[offset * 4 + 1] = color;
|
self.data[offset + 1] = color;
|
||||||
self.data[offset * 4 + 2] = color;
|
self.data[offset + 2] = color;
|
||||||
self.data[offset * 4 + 3] = 255;
|
self.data[offset + 3] = 255;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resize(&mut self, width: u32, height: u32) {
|
pub fn resize(&mut self, width: usize, height: usize) {
|
||||||
self.data = vec![0; (width * height * 4) as usize];
|
self.data = vec![0; width * height * 4];
|
||||||
self.width = width;
|
self.width = width;
|
||||||
self.height = height;
|
self.height = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn width(&self) -> u32 {
|
pub fn width(&self) -> usize {
|
||||||
self.width
|
self.width
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn height(&self) -> u32 {
|
pub fn height(&self) -> usize {
|
||||||
self.height
|
self.height
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,8 +68,8 @@ impl ImgBuffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ImgBuffer {
|
impl ImgBuffer {
|
||||||
pub fn set_pixel(&mut self, x: u32, y: u32, color: (u8, u8, u8)) {
|
pub fn set_pixel(&mut self, x: usize, y: usize, color: (u8, u8, u8)) {
|
||||||
let index = ((y * self.width + x) * 4) as usize;
|
let index = (y * self.width + x) * 4;
|
||||||
self.data[index] = color.0;
|
self.data[index] = color.0;
|
||||||
self.data[index + 1] = color.1;
|
self.data[index + 1] = color.1;
|
||||||
self.data[index + 2] = color.2;
|
self.data[index + 2] = color.2;
|
||||||
|
|||||||
+3
-3
@@ -6,13 +6,13 @@ pub use renderer::Renderer;
|
|||||||
|
|
||||||
pub const RESOLUTION: u32 = 2000;
|
pub const RESOLUTION: u32 = 2000;
|
||||||
|
|
||||||
pub fn calc_resolution(width: u32, height: u32) -> (u32, u32, f32, f32) {
|
pub fn calc_resolution(width: usize, height: usize) -> (usize, usize, f32, f32) {
|
||||||
let aspect = width as f32 / height as f32;
|
let aspect = width as f32 / height as f32;
|
||||||
|
|
||||||
let (distortion_x, distortion_y) = (aspect, 1. / aspect);
|
let (distortion_x, distortion_y) = (aspect, 1. / aspect);
|
||||||
|
|
||||||
let new_width = (distortion_x * RESOLUTION as f32).round() as u32;
|
let new_width = (distortion_x * RESOLUTION as f32).round() as usize;
|
||||||
let new_height = (distortion_y * RESOLUTION as f32).round() as u32;
|
let new_height = (distortion_y * RESOLUTION as f32).round() as usize;
|
||||||
(new_width, new_height, distortion_x, distortion_y)
|
(new_width, new_height, distortion_x, distortion_y)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -15,10 +15,10 @@ pub struct Renderer {
|
|||||||
pub(crate) img: ImgBuffer,
|
pub(crate) img: ImgBuffer,
|
||||||
pub(crate) rand: Rnd,
|
pub(crate) rand: Rnd,
|
||||||
|
|
||||||
pub canvas_width: u32,
|
pub canvas_width: usize,
|
||||||
pub canvas_height: u32,
|
pub canvas_height: usize,
|
||||||
pub actual_width: u32,
|
pub actual_width: usize,
|
||||||
pub actual_height: u32,
|
pub actual_height: usize,
|
||||||
|
|
||||||
pub distortion_x: f32,
|
pub distortion_x: f32,
|
||||||
pub distortion_y: f32,
|
pub distortion_y: f32,
|
||||||
@@ -27,7 +27,7 @@ pub struct Renderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Renderer {
|
impl Renderer {
|
||||||
pub fn new(canvas: &web_sys::HtmlCanvasElement, width: u32, height: u32) -> Self {
|
pub fn new(canvas: &web_sys::HtmlCanvasElement, width: usize, height: usize) -> Self {
|
||||||
let (cwidth, cheight, dist_x, dist_y) = calc_resolution(width, height);
|
let (cwidth, cheight, dist_x, dist_y) = calc_resolution(width, height);
|
||||||
|
|
||||||
// let ctx = canvas.get_context("2d").unwrap().unwrap();
|
// let ctx = canvas.get_context("2d").unwrap().unwrap();
|
||||||
@@ -55,7 +55,7 @@ impl Renderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resize(&mut self, width: u32, height: u32) {
|
pub fn resize(&mut self, width: usize, height: usize) {
|
||||||
let (cwidth, cheight, dist_x, dist_y) = calc_resolution(width, height);
|
let (cwidth, cheight, dist_x, dist_y) = calc_resolution(width, height);
|
||||||
|
|
||||||
self.canvas_width = cwidth;
|
self.canvas_width = cwidth;
|
||||||
@@ -69,15 +69,15 @@ impl Renderer {
|
|||||||
self.ratio_y = cheight as f32 / height as f32;
|
self.ratio_y = cheight as f32 / height as f32;
|
||||||
|
|
||||||
self.img.resize(cwidth, cheight);
|
self.img.resize(cwidth, cheight);
|
||||||
self.ctx.canvas().unwrap().set_width(cwidth);
|
self.ctx.canvas().unwrap().set_width(cwidth as u32);
|
||||||
self.ctx.canvas().unwrap().set_height(cheight);
|
self.ctx.canvas().unwrap().set_height(cheight as u32);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self) {
|
pub fn update(&mut self) {
|
||||||
let data = ImageData::new_with_u8_clamped_array_and_sh(
|
let data = ImageData::new_with_u8_clamped_array_and_sh(
|
||||||
Clamped(&self.img.data),
|
Clamped(&self.img.data),
|
||||||
self.img.width(),
|
self.img.width() as u32,
|
||||||
self.img.height(),
|
self.img.height() as u32,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
self.ctx.put_image_data(&data, 0., 0.).unwrap();
|
self.ctx.put_image_data(&data, 0., 0.).unwrap();
|
||||||
@@ -128,7 +128,7 @@ impl Renderer {
|
|||||||
|
|
||||||
for x in leftx as i32..rightx as i32 {
|
for x in leftx as i32..rightx as i32 {
|
||||||
for y in topy as i32..bottomy as i32 {
|
for y in topy as i32..bottomy as i32 {
|
||||||
self.img.set_pixel(x as u32, y as u32, color);
|
self.img.set_pixel(x as usize, y as usize, color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -163,7 +163,7 @@ impl Renderer {
|
|||||||
for x in leftx as i32..rightx as i32 {
|
for x in leftx as i32..rightx as i32 {
|
||||||
for y in topy as i32..bottomy as i32 {
|
for y in topy as i32..bottomy as i32 {
|
||||||
if self.screen_dist_sq(x as f32, y as f32, cx, cy) <= r2 {
|
if self.screen_dist_sq(x as f32, y as f32, cx, cy) <= r2 {
|
||||||
self.img.set_pixel(x as u32, y as u32, color);
|
self.img.set_pixel(x as usize, y as usize, color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,29 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
render::Renderer,
|
render::Renderer,
|
||||||
views::{Bounds, View},
|
views::{
|
||||||
|
Bounds, LayoutView, View,
|
||||||
|
constraint_layout::{Constraint, ConstraintView},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ColorRectView {
|
pub struct ColorRectView {
|
||||||
color: (u8, u8, u8),
|
color: (u8, u8, u8),
|
||||||
|
bounds: (Option<Bounds>, Option<Bounds>),
|
||||||
|
constraints: (
|
||||||
|
Option<Constraint>,
|
||||||
|
Option<Constraint>,
|
||||||
|
Option<Constraint>,
|
||||||
|
Option<Constraint>,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ColorRectView {
|
impl ColorRectView {
|
||||||
pub fn new(r: u8, g: u8, b: u8) -> Self {
|
pub fn new(r: u8, g: u8, b: u8) -> Self {
|
||||||
Self { color: (r, g, b) }
|
Self {
|
||||||
|
color: (r, g, b),
|
||||||
|
bounds: (None, None),
|
||||||
|
constraints: (None, None, None, None),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,7 +32,44 @@ impl View for ColorRectView {
|
|||||||
renderer.rect_xywh(x as i32, y as i32, w as i32, h as i32, self.color);
|
renderer.rect_xywh(x as i32, y as i32, w as i32, h as i32, self.color);
|
||||||
// log!("Draw");
|
// log!("Draw");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayoutView for ColorRectView {
|
||||||
|
fn set_bounds(&mut self, width: Option<Bounds>, height: Option<Bounds>) {
|
||||||
|
self.bounds = (width, height);
|
||||||
|
}
|
||||||
fn bounds(&self, _ph: f32, _pw: f32) -> (Bounds, Bounds) {
|
fn bounds(&self, _ph: f32, _pw: f32) -> (Bounds, Bounds) {
|
||||||
(Bounds::MatchParent, Bounds::Pixels(200.))
|
(
|
||||||
|
*self.bounds.0.as_ref().unwrap_or(&Bounds::Pixels(100.)),
|
||||||
|
self.bounds.1.unwrap_or(Bounds::Pixels(100.)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConstraintView for ColorRectView {
|
||||||
|
fn set_constraints(
|
||||||
|
&mut self,
|
||||||
|
top: Option<Constraint>,
|
||||||
|
left: Option<Constraint>,
|
||||||
|
right: Option<Constraint>,
|
||||||
|
bottom: Option<Constraint>,
|
||||||
|
) {
|
||||||
|
self.constraints = (top, left, right, bottom)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_constraints(
|
||||||
|
&self,
|
||||||
|
) -> (
|
||||||
|
&Option<Constraint>,
|
||||||
|
&Option<Constraint>,
|
||||||
|
&Option<Constraint>,
|
||||||
|
&Option<Constraint>,
|
||||||
|
) {
|
||||||
|
(
|
||||||
|
&self.constraints.0,
|
||||||
|
&self.constraints.1,
|
||||||
|
&self.constraints.2,
|
||||||
|
&self.constraints.3,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,331 @@
|
|||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
log,
|
||||||
|
render::Renderer,
|
||||||
|
views::{LayoutView, View},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub enum Constraint {
|
||||||
|
Left(f32, usize),
|
||||||
|
Right(f32, usize),
|
||||||
|
Top(f32, usize),
|
||||||
|
Bottom(f32, usize),
|
||||||
|
|
||||||
|
LeftParent(f32),
|
||||||
|
RightParent(f32),
|
||||||
|
TopParent(f32),
|
||||||
|
BottomParent(f32),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ConstraintView: LayoutView {
|
||||||
|
fn set_constraints(
|
||||||
|
&mut self,
|
||||||
|
top: Option<Constraint>,
|
||||||
|
left: Option<Constraint>,
|
||||||
|
right: Option<Constraint>,
|
||||||
|
bottom: Option<Constraint>,
|
||||||
|
);
|
||||||
|
fn get_constraints(
|
||||||
|
&self,
|
||||||
|
) -> (
|
||||||
|
&Option<Constraint>,
|
||||||
|
&Option<Constraint>,
|
||||||
|
&Option<Constraint>,
|
||||||
|
&Option<Constraint>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ConstraintLayout {
|
||||||
|
pub children: Vec<Box<dyn ConstraintView>>,
|
||||||
|
child_positions: Option<Vec<Rect>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Rect {
|
||||||
|
x: f32,
|
||||||
|
y: f32,
|
||||||
|
width: f32,
|
||||||
|
height: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConstraintLayout {
|
||||||
|
pub fn new(children: Vec<Box<dyn ConstraintView>>) -> Self {
|
||||||
|
for child in &children {
|
||||||
|
let (top, left, right, bottom) = child.get_constraints();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
top.is_some() || bottom.is_some(),
|
||||||
|
"Vertically underconstrained"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
left.is_some() || right.is_some(),
|
||||||
|
"Horizontally underconstrained"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let this = ConstraintLayout {
|
||||||
|
children,
|
||||||
|
child_positions: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.test();
|
||||||
|
|
||||||
|
this
|
||||||
|
}
|
||||||
|
pub fn test(&self) {
|
||||||
|
let dependencies = self
|
||||||
|
.children
|
||||||
|
.iter()
|
||||||
|
.map(|c| {
|
||||||
|
let constraints = c.get_constraints();
|
||||||
|
[constraints.0, constraints.1, constraints.2, constraints.3]
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let dac = topological_sort(&dependencies);
|
||||||
|
|
||||||
|
for i in 0..dac.len() {
|
||||||
|
log!("{:?}", dac[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl View for ConstraintLayout {
|
||||||
|
fn draw(&mut self, renderer: &mut Renderer, x: f32, y: f32, self_w: f32, self_h: f32) {
|
||||||
|
let self_top = y;
|
||||||
|
let self_left = x;
|
||||||
|
let self_right = x + self_w;
|
||||||
|
let self_bottom = y + self_h;
|
||||||
|
|
||||||
|
let mut calculated_positions: HashMap<usize, Rect> = HashMap::new();
|
||||||
|
|
||||||
|
let dependencies = self
|
||||||
|
.children
|
||||||
|
.iter()
|
||||||
|
.map(|c| {
|
||||||
|
let constraints = c.get_constraints();
|
||||||
|
[constraints.0, constraints.1, constraints.2, constraints.3]
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let sorted = topological_sort(&dependencies);
|
||||||
|
|
||||||
|
for idx in sorted {
|
||||||
|
let child = &mut self.children[idx];
|
||||||
|
|
||||||
|
let (c_top, c_left, c_right, c_bottom) = child.get_constraints();
|
||||||
|
let (c_width, c_height) = child.bounds(self_w, self_h);
|
||||||
|
|
||||||
|
let c_width = match c_width {
|
||||||
|
crate::views::Bounds::MatchParent => self_w,
|
||||||
|
crate::views::Bounds::Pixels(x) => x,
|
||||||
|
};
|
||||||
|
|
||||||
|
let c_height = match c_height {
|
||||||
|
crate::views::Bounds::MatchParent => self_h,
|
||||||
|
crate::views::Bounds::Pixels(x) => x,
|
||||||
|
};
|
||||||
|
|
||||||
|
let (y, height) = match (c_top, c_bottom) {
|
||||||
|
(None, None) => unreachable!("Vertically unconstrained"),
|
||||||
|
(Some(Constraint::TopParent(top_margin)), None) => {
|
||||||
|
(self_top + top_margin, c_height)
|
||||||
|
}
|
||||||
|
(Some(Constraint::BottomParent(top_margin)), None) => {
|
||||||
|
(self_top + top_margin + c_height, c_height)
|
||||||
|
}
|
||||||
|
(None, Some(Constraint::TopParent(top_margin))) => {
|
||||||
|
(self_top - top_margin, c_height)
|
||||||
|
}
|
||||||
|
(None, Some(Constraint::BottomParent(bottom_margin))) => {
|
||||||
|
(self_h - (bottom_margin + c_height), c_height)
|
||||||
|
}
|
||||||
|
(Some(Constraint::TopParent(_)), Some(Constraint::TopParent(_))) => unreachable!(),
|
||||||
|
(Some(Constraint::BottomParent(_)), Some(Constraint::BottomParent(_))) => {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
(Some(Constraint::BottomParent(_)), Some(Constraint::TopParent(_))) => {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
(Some(Constraint::TopParent(_)), Some(Constraint::BottomParent(top_margin))) => {
|
||||||
|
(self_top + top_margin + c_height, c_height)
|
||||||
|
}
|
||||||
|
_ => todo!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(height >= 0.);
|
||||||
|
|
||||||
|
let x_align = |constraint: &Option<Constraint>| match constraint {
|
||||||
|
Some(Constraint::LeftParent(margin)) => Some(self_left + margin),
|
||||||
|
Some(Constraint::RightParent(margin)) => Some(self_right - margin),
|
||||||
|
Some(Constraint::Left(margin, id)) => {
|
||||||
|
let other = calculated_positions.get(&id).unwrap();
|
||||||
|
Some(other.x - margin - c_width)
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Constraint::Right(margin, id)) => {
|
||||||
|
let other = calculated_positions.get(&id).unwrap();
|
||||||
|
Some(other.x + other.width + margin)
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let x = match (x_align(c_left), x_align(c_right)) {
|
||||||
|
(Some(left), Some(right)) => (right + left - c_width) / 2.,
|
||||||
|
(Some(left), None) => left,
|
||||||
|
(None, Some(right)) => right - c_width,
|
||||||
|
(None, None) => unreachable!("Horizontally unconstrained"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// let (x, width) = match (c_left, c_right) {
|
||||||
|
// (None, None) => unreachable!("Vertically unconstrained"),
|
||||||
|
// (Some(Constraint::LeftParent(left_margin)), None) => {
|
||||||
|
// (self_top + left_margin, width)
|
||||||
|
// }
|
||||||
|
// (Some(Constraint::RightParent(right_margin)), None) => {
|
||||||
|
// (self_top - right_margin + width, width)
|
||||||
|
// }
|
||||||
|
// (None, Some(Constraint::LeftParent(left_margin))) => {
|
||||||
|
// (self_top - left_margin, width)
|
||||||
|
// }
|
||||||
|
// (None, Some(Constraint::RightParent(right_margin))) => {
|
||||||
|
// (self_w - (right_margin + width), width)
|
||||||
|
// // (self_top + top_margin + height, height)
|
||||||
|
// }
|
||||||
|
// (Some(Constraint::Left(left_margin, n)), None) => {
|
||||||
|
// let other = calculated_positions.get(n).unwrap();
|
||||||
|
// (other.x - (width + left_margin), width)
|
||||||
|
// }
|
||||||
|
// (Some(Constraint::Right(right_margin, idx)), None) => {
|
||||||
|
// (self_top - right_margin + width, width)
|
||||||
|
// }
|
||||||
|
// (None, Some(Constraint::Left(left_margin, idx))) => (self_top - left_margin, width),
|
||||||
|
// (None, Some(Constraint::Right(right_margin, idx))) => {
|
||||||
|
// (self_w - (right_margin + width), width)
|
||||||
|
// // (self_top + top_margin + height, height)
|
||||||
|
// }
|
||||||
|
// (Some(Constraint::LeftParent(_)), Some(Constraint::LeftParent(_))) => {
|
||||||
|
// unreachable!()
|
||||||
|
// }
|
||||||
|
// (Some(Constraint::RightParent(_)), Some(Constraint::RightParent(_))) => {
|
||||||
|
// unreachable!()
|
||||||
|
// }
|
||||||
|
// (Some(Constraint::RightParent(_)), Some(Constraint::LeftParent(_))) => {
|
||||||
|
// unreachable!()
|
||||||
|
// }
|
||||||
|
// (Some(Constraint::TopParent(_)), Some(Constraint::BottomParent(top_margin))) => {
|
||||||
|
// (self_top + top_margin + width, width)
|
||||||
|
// }
|
||||||
|
// _ => todo!(),
|
||||||
|
// };
|
||||||
|
|
||||||
|
assert!(c_width >= 0.);
|
||||||
|
|
||||||
|
calculated_positions.insert(
|
||||||
|
idx,
|
||||||
|
Rect {
|
||||||
|
x: x,
|
||||||
|
y: y,
|
||||||
|
width: c_width,
|
||||||
|
height: height,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
child.draw(renderer, x, y, c_width, height);
|
||||||
|
|
||||||
|
// match c_top {
|
||||||
|
// None => {}
|
||||||
|
// Some(Constraint::TopParent(top_margin)) => {
|
||||||
|
|
||||||
|
// // let child_top = self_top + top_margin;
|
||||||
|
// // let child_left = self_left;
|
||||||
|
// // let child_right = self_right;
|
||||||
|
// // let child_bottom = child_top + child.get_height();
|
||||||
|
|
||||||
|
// // child.draw(renderer, child_left, child_top, child_right - child_left, child_bottom - child_top);
|
||||||
|
// }
|
||||||
|
// _ => unreachable!(),
|
||||||
|
// }
|
||||||
|
|
||||||
|
// match
|
||||||
|
|
||||||
|
// let child_top = child_top.unwrap_or(self_top);
|
||||||
|
// let child_left = child_left.unwrap_or(self_left);
|
||||||
|
// let child_right = child_right.unwrap_or(self_right);
|
||||||
|
// let child_bottom = child_bottom.unwrap_or(self_bottom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
|
enum VisitState {
|
||||||
|
Unvisited,
|
||||||
|
Visiting,
|
||||||
|
Visited,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs a topological sort on objects with constraints.
|
||||||
|
/// Returns indices in topologically sorted order (dependencies before dependents).
|
||||||
|
/// Panics if a cycle is detected.
|
||||||
|
pub fn topological_sort(objects: &Vec<[&Option<Constraint>; 4]>) -> Vec<usize> {
|
||||||
|
let n = objects.len();
|
||||||
|
let mut states = vec![VisitState::Unvisited; n];
|
||||||
|
let mut result = Vec::with_capacity(n);
|
||||||
|
|
||||||
|
for i in 0..n {
|
||||||
|
if states[i] == VisitState::Unvisited {
|
||||||
|
dfs(i, objects, &mut states, &mut result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dfs(
|
||||||
|
node: usize,
|
||||||
|
objects: &Vec<[&Option<Constraint>; 4]>,
|
||||||
|
states: &mut Vec<VisitState>,
|
||||||
|
result: &mut Vec<usize>,
|
||||||
|
) {
|
||||||
|
if states[node] == VisitState::Visiting {
|
||||||
|
panic!("Cycle detected in constraint graph at node {}", node);
|
||||||
|
}
|
||||||
|
|
||||||
|
if states[node] == VisitState::Visited {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
states[node] = VisitState::Visiting;
|
||||||
|
|
||||||
|
// Visit all dependencies
|
||||||
|
for constraint in &objects[node] {
|
||||||
|
if let Some(c) = constraint {
|
||||||
|
if let Some(dep) = get_dependency(c) {
|
||||||
|
if dep >= objects.len() {
|
||||||
|
panic!("Invalid dependency index {} in node {}", dep, node);
|
||||||
|
}
|
||||||
|
dfs(dep, objects, states, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
states[node] = VisitState::Visited;
|
||||||
|
result.push(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts the dependency index from a constraint, if it has one.
|
||||||
|
fn get_dependency(constraint: &Constraint) -> Option<usize> {
|
||||||
|
match constraint {
|
||||||
|
Constraint::Left(_, idx) => Some(*idx),
|
||||||
|
Constraint::Right(_, idx) => Some(*idx),
|
||||||
|
Constraint::Top(_, idx) => Some(*idx),
|
||||||
|
Constraint::Bottom(_, idx) => Some(*idx),
|
||||||
|
_ => None, // Parent constraints have no dependencies
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// impl LayoutView for ConstraintLayout {
|
||||||
|
// fn set_bounds(&mut self, width: Option<Bounds>, height: Option<Bounds>) {
|
||||||
|
// // Implement bounds setting logic here
|
||||||
|
// }
|
||||||
|
// }
|
||||||
+60
-6
@@ -1,6 +1,10 @@
|
|||||||
use crate::render::Renderer;
|
use crate::{
|
||||||
|
render::Renderer,
|
||||||
|
views::constraint_layout::{Constraint, ConstraintLayout, ConstraintView},
|
||||||
|
};
|
||||||
|
|
||||||
mod color_rect_view;
|
mod color_rect_view;
|
||||||
|
mod constraint_layout;
|
||||||
mod text_view;
|
mod text_view;
|
||||||
mod vertical_layout;
|
mod vertical_layout;
|
||||||
|
|
||||||
@@ -10,22 +14,72 @@ use vertical_layout::VerticalLayout;
|
|||||||
|
|
||||||
pub trait View {
|
pub trait View {
|
||||||
fn draw(&mut self, renderer: &mut Renderer, x: f32, y: f32, w: f32, h: f32);
|
fn draw(&mut self, renderer: &mut Renderer, x: f32, y: f32, w: f32, h: f32);
|
||||||
fn bounds(&self, pw: f32, ph: f32) -> (Bounds, Bounds);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub enum Bounds {
|
pub enum Bounds {
|
||||||
MatchParent,
|
MatchParent,
|
||||||
Pixels(f32),
|
Pixels(f32),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait LayoutView: View {
|
||||||
|
fn set_bounds(&mut self, width: Option<Bounds>, height: Option<Bounds>);
|
||||||
|
fn bounds(&self, pw: f32, ph: f32) -> (Bounds, Bounds);
|
||||||
|
}
|
||||||
|
|
||||||
// pub trait ViewPosition: Drawable {
|
// pub trait ViewPosition: Drawable {
|
||||||
// // fn draw(&self)
|
// // fn draw(&self)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
pub fn default_view() -> Box<dyn View> {
|
pub fn default_view() -> Box<dyn View> {
|
||||||
Box::new(VerticalLayout::new(vec![
|
let mut a = ColorRectView::new(127, 0, 0);
|
||||||
Box::new(ColorRectView::new(12, 34, 56)),
|
|
||||||
Box::new(TextView::new("Testing!\n12345".to_string())),
|
let temp_margin = 10.0;
|
||||||
Box::new(ColorRectView::new(20, 60, 80)),
|
|
||||||
|
a.set_constraints(
|
||||||
|
Some(Constraint::TopParent(temp_margin)),
|
||||||
|
None,
|
||||||
|
Some(Constraint::Left(temp_margin, 2)),
|
||||||
|
Some(Constraint::BottomParent(temp_margin)),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut b = ColorRectView::new(0, 127, 0);
|
||||||
|
|
||||||
|
b.set_constraints(
|
||||||
|
Some(Constraint::TopParent(temp_margin)),
|
||||||
|
None,
|
||||||
|
Some(Constraint::RightParent(temp_margin)),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut c = ColorRectView::new(0, 0, 127);
|
||||||
|
|
||||||
|
c.set_constraints(
|
||||||
|
None,
|
||||||
|
Some(Constraint::LeftParent(temp_margin)),
|
||||||
|
Some(Constraint::RightParent(temp_margin)),
|
||||||
|
Some(Constraint::BottomParent(temp_margin)),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut d = ColorRectView::new(127, 127, 0);
|
||||||
|
|
||||||
|
d.set_constraints(
|
||||||
|
None,
|
||||||
|
Some(Constraint::LeftParent(temp_margin)),
|
||||||
|
None,
|
||||||
|
Some(Constraint::BottomParent(temp_margin)),
|
||||||
|
);
|
||||||
|
|
||||||
|
Box::new(ConstraintLayout::new(vec![
|
||||||
|
Box::new(a),
|
||||||
|
Box::new(b),
|
||||||
|
Box::new(c),
|
||||||
|
Box::new(d),
|
||||||
]))
|
]))
|
||||||
|
|
||||||
|
// Box::new(VerticalLayout::new(vec![
|
||||||
|
// Box::new(ColorRectView::new(12, 34, 56)),
|
||||||
|
// Box::new(TextView::new("Testing!\n12345".to_string())),
|
||||||
|
// Box::new(ColorRectView::new(20, 60, 80)),
|
||||||
|
// ]))
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-12
@@ -2,9 +2,8 @@ use fontdue::layout::{CoordinateSystem, Layout, LayoutSettings, TextStyle};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
fonts::{FONTS, FontHandle},
|
fonts::{FONTS, FontHandle},
|
||||||
log,
|
|
||||||
render::{Renderer, buffer::Bitmap},
|
render::{Renderer, buffer::Bitmap},
|
||||||
views::{Bounds, View},
|
views::{Bounds, LayoutView, View},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct TextView {
|
pub struct TextView {
|
||||||
@@ -12,6 +11,7 @@ pub struct TextView {
|
|||||||
text: String,
|
text: String,
|
||||||
scale: f32,
|
scale: f32,
|
||||||
font: FontHandle,
|
font: FontHandle,
|
||||||
|
bounds: (Option<Bounds>, Option<Bounds>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextView {
|
impl TextView {
|
||||||
@@ -22,12 +22,22 @@ impl TextView {
|
|||||||
..LayoutSettings::default()
|
..LayoutSettings::default()
|
||||||
});
|
});
|
||||||
|
|
||||||
Self {
|
let mut s = Self {
|
||||||
layout,
|
layout,
|
||||||
text,
|
text,
|
||||||
scale: 20.,
|
scale: 20.,
|
||||||
font: FontHandle::AtiksonHyperlegibleRegular,
|
font: FontHandle::AtiksonHyperlegibleRegular,
|
||||||
}
|
bounds: (None, None),
|
||||||
|
};
|
||||||
|
|
||||||
|
let font = (s.font as usize).clone();
|
||||||
|
|
||||||
|
s.layout.clear();
|
||||||
|
|
||||||
|
s.layout
|
||||||
|
.append(&FONTS, &TextStyle::new(&s.text, s.scale, font));
|
||||||
|
|
||||||
|
s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,13 +56,6 @@ impl View for TextView {
|
|||||||
|
|
||||||
let (x, y) = renderer.undistort(x as f32, y as f32);
|
let (x, y) = renderer.undistort(x as f32, y as f32);
|
||||||
|
|
||||||
let font = (self.font as usize).clone();
|
|
||||||
|
|
||||||
self.layout.clear();
|
|
||||||
|
|
||||||
self.layout
|
|
||||||
.append(&FONTS, &TextStyle::new(&self.text, self.scale, font));
|
|
||||||
|
|
||||||
let (mut width, mut height): (usize, usize) = (0, 0);
|
let (mut width, mut height): (usize, usize) = (0, 0);
|
||||||
for glyph in self.layout.glyphs() {
|
for glyph in self.layout.glyphs() {
|
||||||
width = width.max(glyph.x as usize + glyph.width);
|
width = width.max(glyph.x as usize + glyph.width);
|
||||||
@@ -79,7 +82,21 @@ impl View for TextView {
|
|||||||
|
|
||||||
// new_bitmap
|
// new_bitmap
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayoutView for TextView {
|
||||||
|
fn set_bounds(&mut self, width: Option<Bounds>, height: Option<Bounds>) {
|
||||||
|
self.bounds = (width, height);
|
||||||
|
}
|
||||||
fn bounds(&self, _ph: f32, _pw: f32) -> (Bounds, Bounds) {
|
fn bounds(&self, _ph: f32, _pw: f32) -> (Bounds, Bounds) {
|
||||||
(Bounds::MatchParent, Bounds::Pixels(200.))
|
// let (x, y) = renderer.undistort(x as f32, y as f32);
|
||||||
|
|
||||||
|
let (mut width, mut height): (usize, usize) = (0, 0);
|
||||||
|
for glyph in self.layout.glyphs() {
|
||||||
|
width = width.max(glyph.x as usize + glyph.width);
|
||||||
|
height = height.max(glyph.y as usize + glyph.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
(Bounds::Pixels(width as f32), Bounds::Pixels(height as f32))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
use crate::views::{Bounds, View};
|
use crate::views::{Bounds, LayoutView, View};
|
||||||
|
|
||||||
pub struct VerticalLayout {
|
pub struct VerticalLayout {
|
||||||
pub views: Vec<Box<dyn View>>,
|
pub views: Vec<Box<dyn LayoutView>>,
|
||||||
|
pub bounds: (Option<Bounds>, Option<Bounds>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VerticalLayout {
|
impl VerticalLayout {
|
||||||
pub fn new(views: Vec<Box<dyn View>>) -> Self {
|
pub fn new(views: Vec<Box<dyn LayoutView>>) -> Self {
|
||||||
Self { views }
|
Self {
|
||||||
|
views,
|
||||||
|
bounds: (None, None),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,6 +38,12 @@ impl View for VerticalLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayoutView for VerticalLayout {
|
||||||
|
fn set_bounds(&mut self, width: Option<Bounds>, height: Option<Bounds>) {
|
||||||
|
self.bounds = (width, height);
|
||||||
|
}
|
||||||
|
|
||||||
fn bounds(&self, pw: f32, ph: f32) -> (Bounds, Bounds) {
|
fn bounds(&self, pw: f32, ph: f32) -> (Bounds, Bounds) {
|
||||||
let (mut maxx, mut totaly): (f32, f32) = (0., 0.);
|
let (mut maxx, mut totaly): (f32, f32) = (0., 0.);
|
||||||
|
|||||||
Reference in New Issue
Block a user