Add Rust desktop UI with Blender-style pane system
Implemented foundational pane system using eframe/egui: - Workspace structure with lightningbeam-core and lightningbeam-editor - Layout data structures matching existing JSON schema - All 8 predefined layouts (Animation, Video Editing, Audio/DAW, etc.) - Recursive pane rendering with visual dividers - Layout switcher menu - Color-coded pane types for visualization Foundation complete for interactive pane operations (resize, split, join).
This commit is contained in:
parent
f28791c2c9
commit
bf007e774e
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Rust build artifacts
|
||||||
|
/target/
|
||||||
|
**/target/
|
||||||
|
|
||||||
|
# Cargo.lock for applications (keep for libraries)
|
||||||
|
# We'll keep it since this is an application
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,26 @@
|
||||||
|
[workspace]
|
||||||
|
resolver = "2"
|
||||||
|
members = [
|
||||||
|
"lightningbeam-editor",
|
||||||
|
"lightningbeam-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[workspace.dependencies]
|
||||||
|
# UI Framework (using eframe for simplified integration)
|
||||||
|
eframe = { version = "0.29", default-features = true, features = ["wgpu"] }
|
||||||
|
|
||||||
|
# GPU Rendering
|
||||||
|
vello = "0.3"
|
||||||
|
wgpu = "22"
|
||||||
|
kurbo = "0.11"
|
||||||
|
peniko = "0.5"
|
||||||
|
|
||||||
|
# Windowing
|
||||||
|
winit = "0.30"
|
||||||
|
|
||||||
|
# Serialization
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
|
||||||
|
# Utilities
|
||||||
|
pollster = "0.3"
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "lightningbeam-core"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde = { workspace = true }
|
||||||
|
serde_json = { workspace = true }
|
||||||
|
|
@ -0,0 +1,128 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// Complete layout definition matching JS schema
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct LayoutDefinition {
|
||||||
|
pub name: String,
|
||||||
|
pub description: String,
|
||||||
|
pub layout: LayoutNode,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub custom: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recursive layout tree node
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(tag = "type", rename_all = "kebab-case")]
|
||||||
|
pub enum LayoutNode {
|
||||||
|
Pane {
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
|
#[serde(rename = "horizontal-grid")]
|
||||||
|
HorizontalGrid {
|
||||||
|
percent: f32,
|
||||||
|
children: [Box<LayoutNode>; 2],
|
||||||
|
},
|
||||||
|
#[serde(rename = "vertical-grid")]
|
||||||
|
VerticalGrid {
|
||||||
|
percent: f32,
|
||||||
|
children: [Box<LayoutNode>; 2],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pane types available in the editor
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum PaneType {
|
||||||
|
Stage,
|
||||||
|
Timeline,
|
||||||
|
TimelineV2,
|
||||||
|
Toolbar,
|
||||||
|
Infopanel,
|
||||||
|
Outliner,
|
||||||
|
Piano,
|
||||||
|
PianoRoll,
|
||||||
|
NodeEditor,
|
||||||
|
PresetBrowser,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PaneType {
|
||||||
|
/// Convert from camelCase name (from JSON)
|
||||||
|
pub fn from_name(name: &str) -> Option<Self> {
|
||||||
|
match name {
|
||||||
|
"stage" => Some(Self::Stage),
|
||||||
|
"timeline" => Some(Self::Timeline),
|
||||||
|
"timelineV2" => Some(Self::TimelineV2),
|
||||||
|
"toolbar" => Some(Self::Toolbar),
|
||||||
|
"infopanel" => Some(Self::Infopanel),
|
||||||
|
"outliner" | "outlineer" => Some(Self::Outliner), // Handle typo in JS
|
||||||
|
"piano" => Some(Self::Piano),
|
||||||
|
"pianoRoll" => Some(Self::PianoRoll),
|
||||||
|
"nodeEditor" => Some(Self::NodeEditor),
|
||||||
|
"presetBrowser" => Some(Self::PresetBrowser),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert to camelCase name (for JSON)
|
||||||
|
pub fn to_name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Stage => "stage",
|
||||||
|
Self::Timeline => "timeline",
|
||||||
|
Self::TimelineV2 => "timelineV2",
|
||||||
|
Self::Toolbar => "toolbar",
|
||||||
|
Self::Infopanel => "infopanel",
|
||||||
|
Self::Outliner => "outliner",
|
||||||
|
Self::Piano => "piano",
|
||||||
|
Self::PianoRoll => "pianoRoll",
|
||||||
|
Self::NodeEditor => "nodeEditor",
|
||||||
|
Self::PresetBrowser => "presetBrowser",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert to kebab-case for display
|
||||||
|
pub fn to_kebab_case(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Stage => "stage",
|
||||||
|
Self::Timeline => "timeline",
|
||||||
|
Self::TimelineV2 => "timeline-v2",
|
||||||
|
Self::Toolbar => "toolbar",
|
||||||
|
Self::Infopanel => "infopanel",
|
||||||
|
Self::Outliner => "outliner",
|
||||||
|
Self::Piano => "piano",
|
||||||
|
Self::PianoRoll => "piano-roll",
|
||||||
|
Self::NodeEditor => "node-editor",
|
||||||
|
Self::PresetBrowser => "preset-browser",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get display name for UI
|
||||||
|
pub fn display_name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Stage => "Stage",
|
||||||
|
Self::Timeline => "Timeline",
|
||||||
|
Self::TimelineV2 => "Timeline V2",
|
||||||
|
Self::Toolbar => "Toolbar",
|
||||||
|
Self::Infopanel => "Info Panel",
|
||||||
|
Self::Outliner => "Outliner",
|
||||||
|
Self::Piano => "Piano",
|
||||||
|
Self::PianoRoll => "Piano Roll",
|
||||||
|
Self::NodeEditor => "Node Editor",
|
||||||
|
Self::PresetBrowser => "Preset Browser",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all pane types
|
||||||
|
pub fn all() -> &'static [Self] {
|
||||||
|
&[
|
||||||
|
Self::Stage,
|
||||||
|
Self::Timeline,
|
||||||
|
Self::TimelineV2,
|
||||||
|
Self::Toolbar,
|
||||||
|
Self::Infopanel,
|
||||||
|
Self::Outliner,
|
||||||
|
Self::Piano,
|
||||||
|
Self::PianoRoll,
|
||||||
|
Self::NodeEditor,
|
||||||
|
Self::PresetBrowser,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
// Lightningbeam Core Library
|
||||||
|
// Shared data structures and types
|
||||||
|
|
||||||
|
pub mod layout;
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
[package]
|
||||||
|
name = "lightningbeam-editor"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
lightningbeam-core = { path = "../lightningbeam-core" }
|
||||||
|
|
||||||
|
# UI Framework
|
||||||
|
eframe = { workspace = true }
|
||||||
|
|
||||||
|
# GPU
|
||||||
|
wgpu = { workspace = true }
|
||||||
|
vello = { workspace = true }
|
||||||
|
kurbo = { workspace = true }
|
||||||
|
peniko = { workspace = true }
|
||||||
|
|
||||||
|
# Windowing
|
||||||
|
winit = { workspace = true }
|
||||||
|
|
||||||
|
# Serialization
|
||||||
|
serde = { workspace = true }
|
||||||
|
serde_json = { workspace = true }
|
||||||
|
|
||||||
|
# Utilities
|
||||||
|
pollster = { workspace = true }
|
||||||
|
|
@ -0,0 +1,210 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Animation",
|
||||||
|
"description": "Drawing tools, timeline, and layers front and center",
|
||||||
|
"layout": {
|
||||||
|
"type": "horizontal-grid",
|
||||||
|
"percent": 10,
|
||||||
|
"children": [
|
||||||
|
{ "type": "pane", "name": "toolbar" },
|
||||||
|
{
|
||||||
|
"type": "vertical-grid",
|
||||||
|
"percent": 70,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "vertical-grid",
|
||||||
|
"percent": 30,
|
||||||
|
"children": [
|
||||||
|
{ "type": "pane", "name": "timelineV2" },
|
||||||
|
{ "type": "pane", "name": "stage" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "type": "pane", "name": "infopanel" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Video Editing",
|
||||||
|
"description": "Clip timeline, source monitor, and effects panel",
|
||||||
|
"layout": {
|
||||||
|
"type": "vertical-grid",
|
||||||
|
"percent": 10,
|
||||||
|
"children": [
|
||||||
|
{ "type": "pane", "name": "toolbar" },
|
||||||
|
{
|
||||||
|
"type": "vertical-grid",
|
||||||
|
"percent": 65,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "horizontal-grid",
|
||||||
|
"percent": 50,
|
||||||
|
"children": [
|
||||||
|
{ "type": "pane", "name": "stage" },
|
||||||
|
{ "type": "pane", "name": "infopanel" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "type": "pane", "name": "timelineV2" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Audio/DAW",
|
||||||
|
"description": "Audio tracks prominent with mixer, node editor, and preset browser",
|
||||||
|
"layout": {
|
||||||
|
"type": "horizontal-grid",
|
||||||
|
"percent": 75,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "vertical-grid",
|
||||||
|
"percent": 50,
|
||||||
|
"children": [
|
||||||
|
{ "type": "pane", "name": "timelineV2" },
|
||||||
|
{ "type": "pane", "name": "nodeEditor" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "type": "pane", "name": "presetBrowser" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Scripting",
|
||||||
|
"description": "Code editor, object hierarchy, and console",
|
||||||
|
"layout": {
|
||||||
|
"type": "vertical-grid",
|
||||||
|
"percent": 10,
|
||||||
|
"children": [
|
||||||
|
{ "type": "pane", "name": "toolbar" },
|
||||||
|
{
|
||||||
|
"type": "horizontal-grid",
|
||||||
|
"percent": 70,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "vertical-grid",
|
||||||
|
"percent": 50,
|
||||||
|
"children": [
|
||||||
|
{ "type": "pane", "name": "stage" },
|
||||||
|
{ "type": "pane", "name": "timelineV2" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "vertical-grid",
|
||||||
|
"percent": 50,
|
||||||
|
"children": [
|
||||||
|
{ "type": "pane", "name": "infopanel" },
|
||||||
|
{ "type": "pane", "name": "outlineer" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Rigging",
|
||||||
|
"description": "Viewport focused with bone controls and weight painting",
|
||||||
|
"layout": {
|
||||||
|
"type": "vertical-grid",
|
||||||
|
"percent": 10,
|
||||||
|
"children": [
|
||||||
|
{ "type": "pane", "name": "toolbar" },
|
||||||
|
{
|
||||||
|
"type": "horizontal-grid",
|
||||||
|
"percent": 75,
|
||||||
|
"children": [
|
||||||
|
{ "type": "pane", "name": "stage" },
|
||||||
|
{
|
||||||
|
"type": "vertical-grid",
|
||||||
|
"percent": 50,
|
||||||
|
"children": [
|
||||||
|
{ "type": "pane", "name": "infopanel" },
|
||||||
|
{ "type": "pane", "name": "timelineV2" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "3D",
|
||||||
|
"description": "3D viewport, camera controls, and lighting panel",
|
||||||
|
"layout": {
|
||||||
|
"type": "vertical-grid",
|
||||||
|
"percent": 10,
|
||||||
|
"children": [
|
||||||
|
{ "type": "pane", "name": "toolbar" },
|
||||||
|
{
|
||||||
|
"type": "horizontal-grid",
|
||||||
|
"percent": 70,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "vertical-grid",
|
||||||
|
"percent": 70,
|
||||||
|
"children": [
|
||||||
|
{ "type": "pane", "name": "stage" },
|
||||||
|
{ "type": "pane", "name": "timelineV2" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "type": "pane", "name": "infopanel" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Drawing/Painting",
|
||||||
|
"description": "Minimal UI - just canvas and drawing tools",
|
||||||
|
"layout": {
|
||||||
|
"type": "vertical-grid",
|
||||||
|
"percent": 8,
|
||||||
|
"children": [
|
||||||
|
{ "type": "pane", "name": "toolbar" },
|
||||||
|
{
|
||||||
|
"type": "horizontal-grid",
|
||||||
|
"percent": 85,
|
||||||
|
"children": [
|
||||||
|
{ "type": "pane", "name": "stage" },
|
||||||
|
{
|
||||||
|
"type": "vertical-grid",
|
||||||
|
"percent": 70,
|
||||||
|
"children": [
|
||||||
|
{ "type": "pane", "name": "infopanel" },
|
||||||
|
{ "type": "pane", "name": "timelineV2" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Shader Editor",
|
||||||
|
"description": "Split between viewport preview and code editor",
|
||||||
|
"layout": {
|
||||||
|
"type": "vertical-grid",
|
||||||
|
"percent": 10,
|
||||||
|
"children": [
|
||||||
|
{ "type": "pane", "name": "toolbar" },
|
||||||
|
{
|
||||||
|
"type": "horizontal-grid",
|
||||||
|
"percent": 50,
|
||||||
|
"children": [
|
||||||
|
{ "type": "pane", "name": "stage" },
|
||||||
|
{
|
||||||
|
"type": "vertical-grid",
|
||||||
|
"percent": 60,
|
||||||
|
"children": [
|
||||||
|
{ "type": "pane", "name": "infopanel" },
|
||||||
|
{ "type": "pane", "name": "timelineV2" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,202 @@
|
||||||
|
use eframe::egui;
|
||||||
|
use lightningbeam_core::layout::{LayoutDefinition, LayoutNode, PaneType};
|
||||||
|
|
||||||
|
fn main() -> eframe::Result {
|
||||||
|
println!("🚀 Starting Lightningbeam Editor...");
|
||||||
|
|
||||||
|
// Load layouts from JSON
|
||||||
|
let layouts = load_layouts();
|
||||||
|
println!("✅ Loaded {} layouts", layouts.len());
|
||||||
|
for layout in &layouts {
|
||||||
|
println!(" - {}: {}", layout.name, layout.description);
|
||||||
|
}
|
||||||
|
|
||||||
|
let options = eframe::NativeOptions {
|
||||||
|
viewport: egui::ViewportBuilder::default()
|
||||||
|
.with_inner_size([1920.0, 1080.0])
|
||||||
|
.with_title("Lightningbeam Editor"),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
eframe::run_native(
|
||||||
|
"Lightningbeam Editor",
|
||||||
|
options,
|
||||||
|
Box::new(move |_cc| Ok(Box::new(EditorApp::new(layouts)))),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_layouts() -> Vec<LayoutDefinition> {
|
||||||
|
let json = include_str!("../assets/layouts.json");
|
||||||
|
serde_json::from_str(json).expect("Failed to parse layouts.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EditorApp {
|
||||||
|
layouts: Vec<LayoutDefinition>,
|
||||||
|
current_layout_index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EditorApp {
|
||||||
|
fn new(layouts: Vec<LayoutDefinition>) -> Self {
|
||||||
|
Self {
|
||||||
|
layouts,
|
||||||
|
current_layout_index: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_layout(&self) -> &LayoutDefinition {
|
||||||
|
&self.layouts[self.current_layout_index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl eframe::App for EditorApp {
|
||||||
|
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||||
|
// Top menu bar
|
||||||
|
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
|
||||||
|
egui::menu::bar(ui, |ui| {
|
||||||
|
ui.menu_button("Layout", |ui| {
|
||||||
|
for (i, layout) in self.layouts.iter().enumerate() {
|
||||||
|
if ui
|
||||||
|
.selectable_label(i == self.current_layout_index, &layout.name)
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
self.current_layout_index = i;
|
||||||
|
ui.close_menu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
ui.label(format!("Current: {}", self.current_layout().name));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Main pane area
|
||||||
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
|
let available_rect = ui.available_rect_before_wrap();
|
||||||
|
render_layout_node(ui, &self.current_layout().layout, available_rect);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recursively render a layout node
|
||||||
|
fn render_layout_node(ui: &mut egui::Ui, node: &LayoutNode, rect: egui::Rect) {
|
||||||
|
match node {
|
||||||
|
LayoutNode::Pane { name } => {
|
||||||
|
render_pane(ui, name, rect);
|
||||||
|
}
|
||||||
|
LayoutNode::HorizontalGrid { percent, children } => {
|
||||||
|
// Split horizontally (left | right)
|
||||||
|
let split_x = rect.left() + (rect.width() * percent / 100.0);
|
||||||
|
|
||||||
|
let left_rect = egui::Rect::from_min_max(
|
||||||
|
rect.min,
|
||||||
|
egui::pos2(split_x, rect.max.y),
|
||||||
|
);
|
||||||
|
|
||||||
|
let right_rect = egui::Rect::from_min_max(
|
||||||
|
egui::pos2(split_x, rect.min.y),
|
||||||
|
rect.max,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Render children
|
||||||
|
render_layout_node(ui, &children[0], left_rect);
|
||||||
|
render_layout_node(ui, &children[1], right_rect);
|
||||||
|
|
||||||
|
// Draw divider
|
||||||
|
ui.painter().vline(
|
||||||
|
split_x,
|
||||||
|
rect.y_range(),
|
||||||
|
egui::Stroke::new(2.0, egui::Color32::from_gray(60)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
LayoutNode::VerticalGrid { percent, children } => {
|
||||||
|
// Split vertically (top / bottom)
|
||||||
|
let split_y = rect.top() + (rect.height() * percent / 100.0);
|
||||||
|
|
||||||
|
let top_rect = egui::Rect::from_min_max(
|
||||||
|
rect.min,
|
||||||
|
egui::pos2(rect.max.x, split_y),
|
||||||
|
);
|
||||||
|
|
||||||
|
let bottom_rect = egui::Rect::from_min_max(
|
||||||
|
egui::pos2(rect.min.x, split_y),
|
||||||
|
rect.max,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Render children
|
||||||
|
render_layout_node(ui, &children[0], top_rect);
|
||||||
|
render_layout_node(ui, &children[1], bottom_rect);
|
||||||
|
|
||||||
|
// Draw divider
|
||||||
|
ui.painter().hline(
|
||||||
|
rect.x_range(),
|
||||||
|
split_y,
|
||||||
|
egui::Stroke::new(2.0, egui::Color32::from_gray(60)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Render a single pane with its content
|
||||||
|
fn render_pane(ui: &mut egui::Ui, pane_name: &str, rect: egui::Rect) {
|
||||||
|
let pane_type = PaneType::from_name(pane_name);
|
||||||
|
|
||||||
|
// Get color for pane type
|
||||||
|
let bg_color = if let Some(pane_type) = pane_type {
|
||||||
|
pane_color(pane_type)
|
||||||
|
} else {
|
||||||
|
egui::Color32::from_rgb(40, 40, 40)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Draw background
|
||||||
|
ui.painter().rect_filled(rect, 0.0, bg_color);
|
||||||
|
|
||||||
|
// Draw border
|
||||||
|
ui.painter().rect_stroke(
|
||||||
|
rect,
|
||||||
|
0.0,
|
||||||
|
egui::Stroke::new(1.0, egui::Color32::from_gray(80)),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Draw pane label
|
||||||
|
let text = if let Some(pane_type) = pane_type {
|
||||||
|
pane_type.display_name()
|
||||||
|
} else {
|
||||||
|
pane_name
|
||||||
|
};
|
||||||
|
|
||||||
|
let text_pos = rect.center() - egui::vec2(40.0, 10.0);
|
||||||
|
ui.painter().text(
|
||||||
|
text_pos,
|
||||||
|
egui::Align2::LEFT_CENTER,
|
||||||
|
text,
|
||||||
|
egui::FontId::proportional(16.0),
|
||||||
|
egui::Color32::WHITE,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Draw pane name in corner
|
||||||
|
let corner_pos = rect.min + egui::vec2(8.0, 8.0);
|
||||||
|
ui.painter().text(
|
||||||
|
corner_pos,
|
||||||
|
egui::Align2::LEFT_TOP,
|
||||||
|
format!("[{}]", pane_name),
|
||||||
|
egui::FontId::monospace(10.0),
|
||||||
|
egui::Color32::from_gray(150),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a color for each pane type for visualization
|
||||||
|
fn pane_color(pane_type: PaneType) -> egui::Color32 {
|
||||||
|
match pane_type {
|
||||||
|
PaneType::Stage => egui::Color32::from_rgb(30, 40, 50),
|
||||||
|
PaneType::Timeline => egui::Color32::from_rgb(40, 30, 50),
|
||||||
|
PaneType::TimelineV2 => egui::Color32::from_rgb(45, 35, 55),
|
||||||
|
PaneType::Toolbar => egui::Color32::from_rgb(50, 40, 30),
|
||||||
|
PaneType::Infopanel => egui::Color32::from_rgb(30, 50, 40),
|
||||||
|
PaneType::Outliner => egui::Color32::from_rgb(40, 50, 30),
|
||||||
|
PaneType::Piano => egui::Color32::from_rgb(50, 30, 40),
|
||||||
|
PaneType::PianoRoll => egui::Color32::from_rgb(55, 35, 45),
|
||||||
|
PaneType::NodeEditor => egui::Color32::from_rgb(30, 45, 50),
|
||||||
|
PaneType::PresetBrowser => egui::Color32::from_rgb(50, 45, 30),
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue