use uuid::Uuid;
use vek::Vec4;
use crate::ui::workspace::NodeId;
use crate::ui::{Drawable, UiView, ViewContext};
#[derive(Debug, Clone)]
pub struct ParamListStyle {
pub rect: [f32; 4], pub fill: Vec4<f32>, pub border: Vec4<f32>, pub radius_px: f32, pub border_px: f32, pub layer: i32, pub title_color: Vec4<f32>, pub title_size: f32, pub label_color: Vec4<f32>, }
#[derive(Debug, Clone)]
pub enum ParamListEntry {
Item {
label: String,
widget: NodeId,
reserve_value_space: bool,
},
Separator {
text: String,
},
}
#[derive(Debug, Clone)]
pub struct ParamList {
pub id: String,
render_id: Uuid,
pub style: ParamListStyle,
pub title: Option<String>, pub title_color: Vec4<f32>, pub title_size: f32, pub title_height: f32, pub item_height: f32, pub spacing: f32, pub label_width: f32, pub padding: f32, pub label_offset: f32, pub entries: Vec<ParamListEntry>, pub label_color: Vec4<f32>, pub label_size: f32, }
impl ParamList {
pub fn new(style: ParamListStyle) -> Self {
let title_color = style.title_color;
let title_size = style.title_size;
let label_color = style.label_color;
Self {
id: String::new(),
render_id: Uuid::new_v4(),
style,
title: None,
title_color,
title_size,
title_height: 30.0,
item_height: 32.0,
spacing: 4.0,
label_width: 100.0,
padding: 8.0,
label_offset: 8.0,
entries: Vec::new(),
label_color,
label_size: 14.0,
}
}
pub fn with_id(mut self, id: impl Into<String>) -> Self {
self.id = id.into();
self
}
pub fn with_item_height(mut self, height: f32) -> Self {
self.item_height = height;
self
}
pub fn with_spacing(mut self, spacing: f32) -> Self {
self.spacing = spacing;
self
}
pub fn with_label_width(mut self, width: f32) -> Self {
self.label_width = width;
self
}
pub fn with_padding(mut self, padding: f32) -> Self {
self.padding = padding;
self
}
pub fn with_label_offset(mut self, offset: f32) -> Self {
self.label_offset = offset;
self
}
pub fn with_label_color(mut self, color: Vec4<f32>) -> Self {
self.label_color = color;
self
}
pub fn with_label_size(mut self, size: f32) -> Self {
self.label_size = size;
self
}
pub fn with_title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
pub fn with_title_color(mut self, color: Vec4<f32>) -> Self {
self.title_color = color;
self
}
pub fn with_title_size(mut self, size: f32) -> Self {
self.title_size = size;
self
}
pub fn with_title_height(mut self, height: f32) -> Self {
self.title_height = height;
self
}
pub fn add_item(&mut self, label: impl Into<String>, widget: NodeId) {
self.add_item_with_value_space(label, widget, true);
}
pub fn add_item_full_width(&mut self, label: impl Into<String>, widget: NodeId) {
self.add_item_with_value_space(label, widget, false);
}
pub fn add_item_with_value_space(
&mut self,
label: impl Into<String>,
widget: NodeId,
reserve_value_space: bool,
) {
self.entries.push(ParamListEntry::Item {
label: label.into(),
widget,
reserve_value_space,
});
self.update_height();
}
pub fn add_separator(&mut self, text: impl Into<String>) {
self.entries
.push(ParamListEntry::Separator { text: text.into() });
self.update_height();
}
pub fn children(&self) -> Vec<NodeId> {
self.entries
.iter()
.filter_map(|entry| match entry {
ParamListEntry::Item { widget, .. } => Some(*widget),
ParamListEntry::Separator { .. } => None,
})
.collect()
}
pub fn update_height(&mut self) {
let height = self.calculate_total_height();
self.style.rect[3] = height;
}
fn content_offset_y(&self) -> f32 {
if self.title.is_some() {
self.title_height
} else {
0.0
}
}
pub fn calculate_min_width(&self, widget_width: f32) -> f32 {
self.padding + self.label_offset + self.label_width + widget_width + 40.0 + self.padding
}
pub fn with_auto_width(mut self, widget_width: f32) -> Self {
let width = self.calculate_min_width(widget_width);
self.style.rect[2] = width;
self
}
pub fn calculate_layout(&self, child_sizes: &[[f32; 2]]) -> Vec<[f32; 4]> {
let [x, _y, w, _] = self.style.rect;
let mut rects = Vec::new();
let mut widget_index = 0;
for (row_index, entry) in self.entries.iter().enumerate() {
let ParamListEntry::Item {
reserve_value_space,
..
} = entry
else {
continue;
};
let Some(&[child_width, _]) = child_sizes.get(widget_index) else {
break;
};
let widget_x = x + self.padding + self.label_width;
let widget_y = self.row_y(row_index);
let widget_h = self.item_height;
let value_space = if *reserve_value_space { 40.0 } else { 0.0 };
let available_width = w - self.padding * 2.0 - self.label_width - value_space;
let final_width = if *reserve_value_space {
child_width.min(available_width)
} else {
available_width
};
rects.push([widget_x, widget_y, final_width, widget_h]);
widget_index += 1;
}
rects
}
pub fn get_label_position(&self, row_index: usize) -> [f32; 2] {
let [x, _, _, _] = self.style.rect;
let label_x = x + self.padding + self.label_offset;
let row_center_y = self.row_y(row_index) + (self.item_height / 2.0);
let label_y = row_center_y - (self.label_size / 2.0);
[label_x, label_y]
}
pub fn get_widget_rect(&self, index: usize, widget_width: f32) -> [f32; 4] {
let Some(row_index) = self.row_index_for_widget(index) else {
return [0.0, 0.0, 0.0, 0.0];
};
let [x, _y, w, _] = self.style.rect;
let widget_x = x + self.padding + self.label_width;
let widget_y = self.row_y(row_index);
let widget_h = self.item_height;
let value_space = self
.entry_at_row(row_index)
.map(|entry| match entry {
ParamListEntry::Item {
reserve_value_space,
..
} if *reserve_value_space => 40.0,
_ => 0.0,
})
.unwrap_or(0.0);
let available_width = w - self.padding * 2.0 - self.label_width - value_space;
let final_width = if value_space > 0.0 {
widget_width.min(available_width)
} else {
available_width
};
[widget_x, widget_y, final_width, widget_h]
}
pub fn calculate_total_height(&self) -> f32 {
let content_y_offset = self.content_offset_y();
let row_count = self.entries.len();
if row_count == 0 {
content_y_offset + self.padding * 2.0
} else {
content_y_offset
+ self.padding * 2.0
+ (row_count as f32 * self.item_height)
+ ((row_count - 1) as f32 * self.spacing)
}
}
pub fn set_position(&mut self, x: f32, y: f32) {
self.style.rect[0] = x;
self.style.rect[1] = y;
}
pub fn get_size(&self) -> [f32; 2] {
[self.style.rect[2], self.style.rect[3]]
}
pub fn widget_count(&self) -> usize {
self.entries
.iter()
.filter(|e| matches!(e, ParamListEntry::Item { .. }))
.count()
}
fn row_y(&self, row_index: usize) -> f32 {
let [_, y, _, _] = self.style.rect;
y + self.content_offset_y()
+ self.padding
+ (row_index as f32 * (self.item_height + self.spacing))
}
fn row_index_for_widget(&self, widget_index: usize) -> Option<usize> {
let mut current_widget = 0;
for (row_index, entry) in self.entries.iter().enumerate() {
if let ParamListEntry::Item { .. } = entry {
if current_widget == widget_index {
return Some(row_index);
}
current_widget += 1;
}
}
None
}
fn entry_at_row(&self, row_index: usize) -> Option<&ParamListEntry> {
self.entries.get(row_index)
}
}
impl UiView for ParamList {
fn build(&mut self, ctx: &mut ViewContext) {
ctx.push(Drawable::Rect {
id: self.render_id,
rect: self.style.rect,
fill: self.style.fill,
border: self.style.border,
radius_px: self.style.radius_px,
border_px: self.style.border_px,
layer: self.style.layer,
});
if let Some(ref title_text) = self.title {
let [x, y, w, _] = self.style.rect;
let title_x = x + self.padding;
let title_y = y + (self.title_height - self.title_size) / 2.0;
ctx.push(Drawable::Text {
id: Uuid::new_v4(),
text: title_text.clone(),
origin: [title_x, title_y],
px_size: self.title_size,
color: self.title_color,
layer: self.style.layer + 1,
});
let separator_y = y + self.title_height - 1.0;
ctx.push(Drawable::Rect {
id: Uuid::new_v4(),
rect: [x + self.padding, separator_y, w - self.padding * 2.0, 1.0],
fill: Vec4::new(0.3, 0.3, 0.35, 1.0),
border: Vec4::new(0.0, 0.0, 0.0, 0.0),
radius_px: 0.0,
border_px: 0.0,
layer: self.style.layer + 1,
});
}
for (row_index, entry) in self.entries.iter().enumerate() {
match entry {
ParamListEntry::Item { label, .. } => {
let [label_x, label_y] = self.get_label_position(row_index);
ctx.push(Drawable::Text {
id: Uuid::new_v4(),
text: label.clone(),
origin: [label_x, label_y],
px_size: self.label_size,
color: self.label_color,
layer: self.style.layer + 1,
});
}
ParamListEntry::Separator { text } => {
let [x, _y, _w, _h] = self.style.rect;
let separator_x = x + self.padding;
let separator_y =
self.row_y(row_index) + (self.item_height - self.title_size) * 0.5;
ctx.push(Drawable::Text {
id: Uuid::new_v4(),
text: text.clone(),
origin: [separator_x, separator_y],
px_size: self.title_size,
color: self.title_color,
layer: self.style.layer + 1,
});
}
}
}
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn view_id(&self) -> &str {
&self.id
}
}