structure for ICED to fill GUI into.
This commit is contained in:
parent
9cebfa128f
commit
d67dc1c5e8
|
|
@ -0,0 +1,66 @@
|
|||
/// applies the editor's layout diffs. each diff is comprised of a widget_path and a new_value. empty path = swap the whole layout.
|
||||
/// otherwise, descend one step each index. tables descend through [row, col] indexed cells.
|
||||
use graphite_editor::messages::layout::utility_types::widget_prelude::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct LayoutStore {
|
||||
layouts: HashMap<LayoutTarget, Layout>,
|
||||
}
|
||||
|
||||
impl LayoutStore {
|
||||
pub fn apply(&mut self, target: LayoutTarget, diff: WidgetDiff) {
|
||||
let layout = self.layouts.entry(target).or_default();
|
||||
apply_diff_layout(layout, &diff.widget_path, diff.new_value);
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&LayoutTarget, &Layout)> {
|
||||
self.layouts.iter()
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_diff_layout(layout: &mut Layout, path: &[usize], new_value: DiffUpdate) {
|
||||
let Some((first, rest)) = path.split_first() else {
|
||||
if let DiffUpdate::Layout(new) = new_value {
|
||||
*layout = new;
|
||||
}
|
||||
return;
|
||||
};
|
||||
if let Some(group) = layout.0.get_mut(*first) {
|
||||
apply_diff_group(group, rest, new_value);
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_diff_group(group: &mut LayoutGroup, path: &[usize], new_value: DiffUpdate) {
|
||||
let Some((first, rest)) = path.split_first() else {
|
||||
if let DiffUpdate::LayoutGroup(new) = new_value {
|
||||
*group = new;
|
||||
}
|
||||
return;
|
||||
};
|
||||
match group {
|
||||
LayoutGroup::Column(WidgetColumn { widgets }) => apply_diff_instance(widgets.get_mut(*first), rest, new_value),
|
||||
LayoutGroup::Row(WidgetRow { widgets }) => apply_diff_instance(widgets.get_mut(*first), rest, new_value),
|
||||
LayoutGroup::Section(WidgetSection { layout, .. }) => {
|
||||
if let Some(child) = layout.0.get_mut(*first) {
|
||||
apply_diff_group(child, rest, new_value);
|
||||
}
|
||||
}
|
||||
LayoutGroup::Table(WidgetTable { rows, .. }) => {
|
||||
if let Some(table_row) = rows.get_mut(*first)
|
||||
&& let Some((col, rest_after_col)) = rest.split_first()
|
||||
{
|
||||
apply_diff_instance(table_row.get_mut(*col), rest_after_col, new_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_diff_instance(slot: Option<&mut WidgetInstance>, rest: &[usize], new_value: DiffUpdate) {
|
||||
if !rest.is_empty() {
|
||||
return;
|
||||
}
|
||||
if let (Some(slot), DiffUpdate::Widget(new)) = (slot, new_value) {
|
||||
*slot = new;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +1,24 @@
|
|||
mod layout;
|
||||
mod widgets;
|
||||
|
||||
use graphite_editor::application::{Editor, Environment, Host, Platform};
|
||||
use graphite_editor::messages::prelude::*;
|
||||
use iced::widget::{column, container, text};
|
||||
use iced::widget::{column, container, scrollable, text};
|
||||
use iced::{Element, Length, Task, Theme};
|
||||
use rand::Rng;
|
||||
|
||||
use crate::layout::LayoutStore;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Message {
|
||||
pub enum Message {
|
||||
Init,
|
||||
WidgetActivated,
|
||||
}
|
||||
|
||||
struct App {
|
||||
editor: Editor,
|
||||
last_frontend_count: usize,
|
||||
store: LayoutStore,
|
||||
frontend_log: Vec<String>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
|
|
@ -23,7 +30,14 @@ impl App {
|
|||
let seed = rand::rng().random();
|
||||
let editor = Editor::new(environment, seed);
|
||||
|
||||
(Self { editor, last_frontend_count: 0 }, Task::done(Message::Init))
|
||||
(
|
||||
Self {
|
||||
editor,
|
||||
store: LayoutStore::default(),
|
||||
frontend_log: Vec::new(),
|
||||
},
|
||||
Task::done(Message::Init),
|
||||
)
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
|
|
@ -34,22 +48,43 @@ impl App {
|
|||
match message {
|
||||
Message::Init => {
|
||||
let responses = self.editor.handle_message(PortfolioMessage::Init);
|
||||
self.last_frontend_count = responses.len();
|
||||
tracing::info!(count = responses.len(), "editor responded to PortfolioMessage::Init");
|
||||
for response in &responses {
|
||||
tracing::info!(kind = %response.to_discriminant().local_name(), "frontend message");
|
||||
}
|
||||
self.absorb(responses);
|
||||
}
|
||||
Message::WidgetActivated => {}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn absorb(&mut self, responses: Vec<FrontendMessage>) {
|
||||
for message in responses {
|
||||
let kind = message.to_discriminant().local_name();
|
||||
match message {
|
||||
FrontendMessage::UpdateLayout { layout_target, diff } => {
|
||||
self.frontend_log.push(format!("UpdateLayout → {layout_target:?} ({} diffs)", diff.len()));
|
||||
for d in diff {
|
||||
self.store.apply(layout_target, d);
|
||||
}
|
||||
}
|
||||
_ => self.frontend_log.push(kind.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<'_, Message> {
|
||||
container(column![text("Graphite").size(40), text(format!("editor produced {} frontend messages at boot", self.last_frontend_count)).size(14),].spacing(8))
|
||||
.padding(24)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
let mut targets = column![].spacing(12);
|
||||
let mut entries: Vec<_> = self.store.iter().collect();
|
||||
entries.sort_by_key(|(target, _)| **target as u8);
|
||||
for (target, layout) in entries {
|
||||
let header = text(format!("{target:?}")).size(14);
|
||||
let body = widgets::render_layout(layout);
|
||||
targets = targets.push(column![header, container(body).padding(6)].spacing(4));
|
||||
}
|
||||
|
||||
let log_body = self.frontend_log.iter().fold(column![].spacing(2), |c, line| c.push(text(line.as_str()).size(11)));
|
||||
|
||||
let log_pane = column![text("frontend messages").size(14), container(log_body).padding(6)].spacing(4);
|
||||
|
||||
container(scrollable(column![targets, log_pane].spacing(16))).padding(16).width(Length::Fill).height(Length::Fill).into()
|
||||
}
|
||||
|
||||
fn theme(&self) -> Theme {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,124 @@
|
|||
use graphite_editor::messages::layout::utility_types::widget_prelude::*;
|
||||
use iced::widget::{Space, button, column, container, row, text};
|
||||
use iced::{Alignment, Element, Length};
|
||||
|
||||
use crate::Message;
|
||||
|
||||
pub fn render_layout(layout: &Layout) -> Element<'_, Message> {
|
||||
let mut col = column![].spacing(4);
|
||||
for group in &layout.0 {
|
||||
col = col.push(render_group(group));
|
||||
}
|
||||
col.into()
|
||||
}
|
||||
|
||||
fn render_group(group: &LayoutGroup) -> Element<'_, Message> {
|
||||
match group {
|
||||
LayoutGroup::Column(WidgetColumn { widgets }) => {
|
||||
let mut col = column![].spacing(4);
|
||||
for instance in widgets {
|
||||
col = col.push(widget_to_element(&instance.widget));
|
||||
}
|
||||
col.into()
|
||||
}
|
||||
LayoutGroup::Row(WidgetRow { widgets }) => {
|
||||
let mut r = row![].spacing(4).align_y(Alignment::Center);
|
||||
for instance in widgets {
|
||||
r = r.push(widget_to_element(&instance.widget));
|
||||
}
|
||||
r.into()
|
||||
}
|
||||
LayoutGroup::Section(WidgetSection { name, layout, .. }) => {
|
||||
let mut col = column![text(name.as_str()).size(12)].spacing(4);
|
||||
for child in &layout.0 {
|
||||
col = col.push(render_group(child));
|
||||
}
|
||||
container(col).padding(6).into()
|
||||
}
|
||||
LayoutGroup::Table(WidgetTable { rows, .. }) => {
|
||||
let mut col = column![].spacing(2);
|
||||
for table_row in rows {
|
||||
let mut r = row![].spacing(4);
|
||||
for instance in table_row {
|
||||
r = r.push(widget_to_element(&instance.widget));
|
||||
}
|
||||
col = col.push(r);
|
||||
}
|
||||
col.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// maps every Widget variant to an ICED element. labels appended for all available items, not just implemented ones.
|
||||
fn widget_to_element(widget: &Widget) -> Element<'_, Message> {
|
||||
match widget {
|
||||
Widget::TextLabel(w) => text(w.value.as_str()).size(13).into(),
|
||||
Widget::IconLabel(w) => text(format!("[{}]", w.icon)).size(13).into(),
|
||||
Widget::ShortcutLabel(_) => text("⌘").size(11).into(),
|
||||
Widget::Separator(w) => match w.direction {
|
||||
SeparatorDirection:i :Horizontal => Space::new().width(Length::Fixed(8.0)).into(),
|
||||
SeparatorDirection::Vertical => Space::new().height(Length::Fixed(8.0)).into(),
|
||||
},
|
||||
Widget::IconButton(w) => button(text(format!("[{}]", w.icon))).on_press(Message::WidgetActivated).into(),
|
||||
Widget::TextButton(w) => button(text(w.label.as_str()).size(13)).on_press(Message::WidgetActivated).into(),
|
||||
Widget::PopoverButton(w) => {
|
||||
let label = w.icon.as_deref().unwrap_or("▾");
|
||||
button(text(format!("[{label}]"))).on_press(Message::WidgetActivated).into()
|
||||
}
|
||||
Widget::CheckboxInput(w) => {
|
||||
let mark = if w.checked { "[x]" } else { "[ ]" };
|
||||
text(mark).size(13).into()
|
||||
}
|
||||
Widget::RadioInput(w) => {
|
||||
let mut r = row![].spacing(4);
|
||||
for entry in &w.entries {
|
||||
let label = if !entry.label.is_empty() {
|
||||
entry.label.as_str()
|
||||
} else if let Some(icon) = entry.icon.as_deref() {
|
||||
icon
|
||||
} else {
|
||||
"●"
|
||||
};
|
||||
r = r.push(button(text(label).size(12)).on_press(Message::WidgetActivated));
|
||||
}
|
||||
r.into()
|
||||
}
|
||||
Widget::DropdownInput(w) => {
|
||||
let current = w
|
||||
.selected_index
|
||||
.and_then(|i| w.entries.iter().flatten().nth(i as usize))
|
||||
.map(|entry| entry.label.as_str())
|
||||
.unwrap_or("▾");
|
||||
button(text(format!("{current} ▾")).size(12)).on_press(Message::WidgetActivated).into()
|
||||
}
|
||||
Widget::NumberInput(w) => {
|
||||
let body = match w.value {
|
||||
Some(v) => format!("{v}"),
|
||||
None => String::from("—"),
|
||||
};
|
||||
let body = if !w.label.is_empty() { format!("{}: {body}", w.label) } else { body };
|
||||
text(body).size(12).into()
|
||||
}
|
||||
Widget::TextInput(w) => text(w.value.as_str()).size(12).into(),
|
||||
Widget::TextAreaInput(w) => text(w.value.as_str()).size(12).into(),
|
||||
Widget::ColorInput(_) => text("◼ color").size(12).into(),
|
||||
Widget::ColorComparisonInput(_) => text("◼◼").size(12).into(),
|
||||
Widget::ColorPresetsInput(_) => text("◼◼◼").size(12).into(),
|
||||
Widget::SpectrumInput(_) => text("[spectrum]").size(11).into(),
|
||||
Widget::VisualColorPickersInput(_) => text("[picker]").size(11).into(),
|
||||
Widget::WorkingColorsInput(_) => text("◼/◻").size(12).into(),
|
||||
Widget::ReferencePointInput(_) => text("·").size(13).into(),
|
||||
Widget::BreadcrumbTrailButtons(w) => {
|
||||
let mut r = row![].spacing(4);
|
||||
for label in &w.labels {
|
||||
r = r.push(text(label.as_str()).size(12));
|
||||
r = r.push(text("›").size(12));
|
||||
}
|
||||
r.into()
|
||||
}
|
||||
Widget::ParameterExposeButton(_) => button(text("●").size(11)).on_press(Message::WidgetActivated).into(),
|
||||
Widget::NodeCatalog(_) => text("[node catalog]").size(11).into(),
|
||||
Widget::ImageButton(_) => button(text("[img]").size(11)).on_press(Message::WidgetActivated).into(),
|
||||
Widget::ImageLabel(_) => text("[img]").size(11).into(),
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue