summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock149
-rw-r--r--Cargo.toml5
-rw-r--r--TODO.md2
-rw-r--r--src/dialogs.rs68
-rw-r--r--src/main.rs188
-rw-r--r--src/types.rs22
7 files changed, 203 insertions, 232 deletions
diff --git a/.gitignore b/.gitignore
index 9545d1d..c61ef9b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
**/target/
**/*.json
+rustc-ice-*.txt
diff --git a/Cargo.lock b/Cargo.lock
index 9b36cfb..ee2f5e1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -185,6 +185,8 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df"
dependencies = [
+ "async-fs",
+ "async-net",
"enumflags2",
"futures-channel",
"futures-util",
@@ -192,7 +194,6 @@ dependencies = [
"raw-window-handle",
"serde",
"serde_repr",
- "tokio",
"url",
"zbus",
]
@@ -404,18 +405,6 @@ dependencies = [
]
[[package]]
-name = "atk-sys"
-version = "0.18.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086"
-dependencies = [
- "glib-sys",
- "gobject-sys",
- "libc",
- "system-deps",
-]
-
-[[package]]
name = "atomic-waker"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -644,16 +633,6 @@ dependencies = [
]
[[package]]
-name = "cairo-sys-rs"
-version = "0.18.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51"
-dependencies = [
- "libc",
- "system-deps",
-]
-
-[[package]]
name = "calloop"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1590,36 +1569,6 @@ dependencies = [
]
[[package]]
-name = "gdk-pixbuf-sys"
-version = "0.18.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7"
-dependencies = [
- "gio-sys",
- "glib-sys",
- "gobject-sys",
- "libc",
- "system-deps",
-]
-
-[[package]]
-name = "gdk-sys"
-version = "0.18.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7"
-dependencies = [
- "cairo-sys-rs",
- "gdk-pixbuf-sys",
- "gio-sys",
- "glib-sys",
- "gobject-sys",
- "libc",
- "pango-sys",
- "pkg-config",
- "system-deps",
-]
-
-[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1681,19 +1630,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
-name = "gio-sys"
-version = "0.18.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2"
-dependencies = [
- "glib-sys",
- "gobject-sys",
- "libc",
- "system-deps",
- "winapi",
-]
-
-[[package]]
name = "gl_generator"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1711,16 +1647,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3"
[[package]]
-name = "glib-sys"
-version = "0.18.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898"
-dependencies = [
- "libc",
- "system-deps",
-]
-
-[[package]]
name = "gloo-timers"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1754,17 +1680,6 @@ dependencies = [
]
[[package]]
-name = "gobject-sys"
-version = "0.18.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44"
-dependencies = [
- "glib-sys",
- "libc",
- "system-deps",
-]
-
-[[package]]
name = "gpu-alloc"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1816,24 +1731,6 @@ dependencies = [
]
[[package]]
-name = "gtk-sys"
-version = "0.18.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414"
-dependencies = [
- "atk-sys",
- "cairo-sys-rs",
- "gdk-pixbuf-sys",
- "gdk-sys",
- "gio-sys",
- "glib-sys",
- "gobject-sys",
- "libc",
- "pango-sys",
- "system-deps",
-]
-
-[[package]]
name = "guillotiere"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2074,6 +1971,7 @@ dependencies = [
"iced",
"iced_anim",
"iced_custom_highlighter",
+ "iced_dialog",
"iced_drop",
"iced_fontello",
"rfd",
@@ -2118,6 +2016,15 @@ dependencies = [
]
[[package]]
+name = "iced_dialog"
+version = "0.14.0-dev"
+source = "git+https://github.com/pml68/iced_dialog?branch=iced%2Fpersonal#6bd6c48b55201116791c59e73779613d617615c7"
+dependencies = [
+ "iced_core",
+ "iced_widget",
+]
+
+[[package]]
name = "iced_drop"
version = "0.1.0"
dependencies = [
@@ -3454,18 +3361,6 @@ dependencies = [
]
[[package]]
-name = "pango-sys"
-version = "0.18.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5"
-dependencies = [
- "glib-sys",
- "gobject-sys",
- "libc",
- "system-deps",
-]
-
-[[package]]
name = "parking"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3642,6 +3537,12 @@ dependencies = [
]
[[package]]
+name = "pollster"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3"
+
+[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4008,16 +3909,15 @@ dependencies = [
"ashpd 0.11.0",
"block2 0.6.0",
"dispatch2",
- "glib-sys",
- "gobject-sys",
- "gtk-sys",
"js-sys",
"log",
"objc2 0.6.0",
"objc2-app-kit 0.3.0",
"objc2-core-foundation",
"objc2-foundation 0.3.0",
+ "pollster",
"raw-window-handle",
+ "urlencoding",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
@@ -4872,9 +4772,7 @@ dependencies = [
"libc",
"mio",
"pin-project-lite",
- "signal-hook-registry",
"socket2",
- "tracing",
"windows-sys 0.52.0",
]
@@ -5157,6 +5055,12 @@ dependencies = [
]
[[package]]
+name = "urlencoding"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
+
+[[package]]
name = "usvg"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -6285,7 +6189,6 @@ dependencies = [
"serde",
"serde_repr",
"static_assertions",
- "tokio",
"tracing",
"uds_windows",
"windows-sys 0.59.0",
diff --git a/Cargo.toml b/Cargo.toml
index 8702fc5..623bb62 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,12 +19,15 @@ iced.workspace = true
iced_anim = { git = "https://github.com/pml68/iced_anim", features = ["derive"] }
iced_custom_highlighter = { git = "https://github.com/pml68/iced_custom_highlighter", branch = "master" }
iced_drop = { path = "iced_drop" }
+iced_dialog = { git = "https://github.com/pml68/iced_dialog", branch = "iced/personal" }
serde = { version = "1.0.217", features = ["derive"] }
serde_json = "1.0.138"
toml = "0.8.20"
tokio = { version = "1.43", features = ["fs"] }
tokio-stream = { version = "0.1", features = ["fs"] }
-rfd = { version = "0.15.2", default-features = false, features = ["tokio", "gtk3"] }
+# TODO: enable tokio when it actually compiles
+# rfd = { version = "0.15.2", default-features = false, features = ["tokio", "xdg-portal"] }
+rfd = "0.15.3"
rust-format = "0.3.4"
fxhash = "0.2.1"
thiserror = "2.0.11"
diff --git a/TODO.md b/TODO.md
index 5ec0a2a..d85bf5e 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1,3 +1,3 @@
- [ ] Make Designer view more usable
- [ ] Rework options backend to work on-project-load
-- [ ] Remove GTK3 dependency
+- [x] Remove GTK3 dependency
diff --git a/src/dialogs.rs b/src/dialogs.rs
index 5954ff5..08513fd 100644
--- a/src/dialogs.rs
+++ b/src/dialogs.rs
@@ -1,35 +1,47 @@
-use rfd::{
- AsyncMessageDialog, MessageButtons, MessageDialog, MessageDialogResult,
- MessageLevel,
-};
+use iced::{Element, Task};
+use iced_dialog::button;
-pub async fn error_dialog(description: impl Into<String>) {
- let _ = AsyncMessageDialog::new()
- .set_level(MessageLevel::Error)
- .set_buttons(MessageButtons::Ok)
- .set_title("Oops! Something went wrong.")
- .set_description(description)
- .show()
- .await;
+use crate::Message;
+use crate::types::{DialogAction, DialogButtons};
+
+pub const UNSAVED_CHANGES_TITLE: &str = "Unsaved changes";
+pub const WARNING_TITLE: &str = "Heads up!";
+pub const ERROR_TITLE: &str = "Oops! Something went wrong.";
+
+pub fn ok_button<'a>() -> Element<'a, Message> {
+ button("Ok").on_press(Message::DialogOk).into()
}
-pub async fn warning_dialog(description: impl Into<String>) {
- let _ = AsyncMessageDialog::new()
- .set_level(MessageLevel::Warning)
- .set_buttons(MessageButtons::Ok)
- .set_title("Heads up!")
- .set_description(description)
- .show()
- .await;
+pub fn cancel_button<'a>() -> Element<'a, Message> {
+ button("Cancel").on_press(Message::DialogCancel).into()
}
-pub fn unsaved_changes_dialog(description: impl Into<String>) -> bool {
- let result = MessageDialog::new()
- .set_level(MessageLevel::Warning)
- .set_buttons(MessageButtons::OkCancel)
- .set_title("Unsaved changes")
- .set_description(description)
- .show();
+pub fn error_dialog(description: impl Into<String>) -> Task<Message> {
+ Task::done(Message::OpenDialog(
+ ERROR_TITLE,
+ description.into(),
+ DialogButtons::Ok,
+ DialogAction::None,
+ ))
+}
+
+pub fn warning_dialog(description: impl Into<String>) -> Task<Message> {
+ Task::done(Message::OpenDialog(
+ WARNING_TITLE,
+ description.into(),
+ DialogButtons::Ok,
+ DialogAction::None,
+ ))
+}
- matches!(result, MessageDialogResult::Ok)
+pub fn unsaved_changes_dialog(
+ description: impl Into<String>,
+ action: DialogAction,
+) -> Task<Message> {
+ Task::done(Message::OpenDialog(
+ UNSAVED_CHANGES_TITLE,
+ description.into(),
+ DialogButtons::OkCancel,
+ action,
+ ))
}
diff --git a/src/main.rs b/src/main.rs
index e566ed5..d5715ef 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -15,16 +15,25 @@ use std::path::PathBuf;
use std::sync::Arc;
use config::Config;
-use dialogs::{error_dialog, unsaved_changes_dialog, warning_dialog};
+use dialogs::{
+ cancel_button, error_dialog, ok_button, unsaved_changes_dialog,
+ warning_dialog,
+};
use error::Error;
use iced::advanced::widget::Id;
-use iced::widget::{Column, container, pane_grid, pick_list, row, text_editor};
+use iced::widget::{
+ Column, container, pane_grid, pick_list, row, text, text_editor,
+};
use iced::{Alignment, Element, Length, Task, Theme, clipboard, keyboard};
use iced_anim::transition::Easing;
use iced_anim::{Animated, Animation};
+use iced_dialog::dialog::Dialog;
use panes::{code_view, designer_view, element_list};
use tokio::runtime;
-use types::{Action, DesignerPane, ElementName, Message, Project};
+use types::{
+ Action, DesignerPane, DialogAction, DialogButtons, ElementName, Message,
+ Project,
+};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let version = std::env::args()
@@ -51,7 +60,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.theme(|state| state.theme.value().clone())
.subscription(App::subscription)
.run_with(move || App::new(config_load))?;
-
Ok(())
}
@@ -65,6 +73,11 @@ struct App {
pane_state: pane_grid::State<Panes>,
focus: Option<pane_grid::Pane>,
designer_page: DesignerPane,
+ dialog_is_open: bool,
+ dialog_title: &'static str,
+ dialog_content: String,
+ dialog_buttons: DialogButtons,
+ dialog_action: DialogAction,
element_list: &'static [ElementName],
editor_content: text_editor::Content,
}
@@ -96,11 +109,10 @@ impl App {
Message::FileOpened,
)
} else {
- Task::future(warning_dialog(format!(
+ warning_dialog(format!(
"The file {} does not exist, or isn't a file.",
path.to_string_lossy()
- )))
- .discard()
+ ))
}
} else {
Task::none()
@@ -117,6 +129,11 @@ impl App {
pane_state: state,
focus: None,
designer_page: DesignerPane::DesignerView,
+ dialog_is_open: false,
+ dialog_title: "",
+ dialog_content: String::new(),
+ dialog_buttons: DialogButtons::None,
+ dialog_action: DialogAction::None,
element_list: ElementName::ALL,
editor_content: text_editor::Content::new(),
},
@@ -146,41 +163,37 @@ impl App {
fn update(&mut self, message: Message) -> Task<Message> {
match message {
- Message::SwitchTheme(event) => {
- self.theme.update(event);
-
- Task::none()
- }
- Message::CopyCode => clipboard::write(self.editor_content.text()),
- Message::SwitchPage(page) => {
- self.designer_page = page;
- Task::none()
+ Message::SwitchTheme(event) => self.theme.update(event),
+ Message::CopyCode => {
+ return clipboard::write(self.editor_content.text());
}
+ Message::SwitchPage(page) => self.designer_page = page,
Message::EditorAction(action) => {
if let text_editor::Action::Scroll { lines: _ } = action {
self.editor_content.perform(action);
}
- Task::none()
}
Message::RefreshEditorContent => {
match self.project.app_code(&self.config) {
Ok(code) => {
self.editor_content =
text_editor::Content::with_text(&code);
- Task::none()
}
- Err(error) => Task::future(error_dialog(error)).discard(),
+ Err(error) => return error_dialog(error),
}
}
Message::DropNewElement(name, point, _) => {
- iced_drop::zones_on_point(
+ return iced_drop::zones_on_point(
move |zones| Message::HandleNew(name.clone(), zones),
point,
None,
None,
- )
+ );
}
Message::HandleNew(name, zones) => {
+ let refresh_editor_task =
+ Task::done(Message::RefreshEditorContent);
+
let ids: Vec<Id> = zones.into_iter().map(|z| z.0).collect();
if !ids.is_empty() {
let eltree_clone = self.project.element_tree.clone();
@@ -195,23 +208,21 @@ impl App {
self.project.element_tree = Some(element.clone());
}
Err(error) => {
- return Task::future(error_dialog(error))
- .map(|_| Message::RefreshEditorContent);
+ return error_dialog(error)
+ .chain(refresh_editor_task);
}
_ => {}
}
- Task::done(Message::RefreshEditorContent)
- } else {
- Task::none()
+ return refresh_editor_task;
}
}
Message::MoveElement(element, point, _) => {
- iced_drop::zones_on_point(
+ return iced_drop::zones_on_point(
move |zones| Message::HandleMove(element.clone(), zones),
point,
None,
None,
- )
+ );
}
Message::HandleMove(element, zones) => {
let ids: Vec<Id> = zones.into_iter().map(|z| z.0).collect();
@@ -227,111 +238,119 @@ impl App {
action,
);
if let Err(error) = result {
- return Task::future(error_dialog(error)).discard();
+ return error_dialog(error);
}
self.is_dirty = true;
- Task::done(Message::RefreshEditorContent)
- } else {
- Task::none()
+ return Task::done(Message::RefreshEditorContent);
}
}
Message::PaneResized(pane_grid::ResizeEvent { split, ratio }) => {
self.pane_state.resize(split, ratio);
- Task::none()
- }
- Message::PaneClicked(pane) => {
- self.focus = Some(pane);
- Task::none()
}
+ Message::PaneClicked(pane) => self.focus = Some(pane),
Message::PaneDragged(pane_grid::DragEvent::Dropped {
pane,
target,
- }) => {
- self.pane_state.drop(pane, target);
- Task::none()
+ }) => self.pane_state.drop(pane, target),
+ Message::PaneDragged(_) => {}
+ Message::OpenDialog(title, content, buttons, action) => {
+ self.dialog_title = title;
+ self.dialog_content = content;
+ self.dialog_buttons = buttons;
+ self.dialog_action = action;
+ self.dialog_is_open = true;
}
- Message::PaneDragged(_) => Task::none(),
- Message::NewFile => {
- if !self.is_loading {
- if !self.is_dirty {
+ Message::CloseDialog => self.dialog_is_open = false,
+ Message::DialogOk => {
+ let close_dialog_task = Task::done(Message::CloseDialog);
+
+ match self.dialog_action {
+ DialogAction::None => {}
+ DialogAction::NewFile => {
+ self.is_dirty = false;
self.project = Project::new();
self.project_path = None;
self.editor_content = text_editor::Content::new();
- } else if unsaved_changes_dialog(
- "You have unsaved changes. Do you wish to discard these and create a new project?",
- ) {
+ }
+ DialogAction::OpenFile => {
self.is_dirty = false;
+ self.is_loading = true;
+ return Task::perform(
+ Project::from_file(self.config.clone()),
+ Message::FileOpened,
+ )
+ .chain(close_dialog_task);
+ }
+ }
+
+ return close_dialog_task;
+ }
+ Message::DialogCancel => return Task::done(Message::CloseDialog),
+ Message::NewFile => {
+ if !self.is_loading {
+ if !self.is_dirty {
self.project = Project::new();
self.project_path = None;
self.editor_content = text_editor::Content::new();
+ } else {
+ return unsaved_changes_dialog(
+ "You have unsaved changes. Do you wish to discard these and create a new project?",
+ DialogAction::NewFile,
+ );
}
}
-
- Task::none()
}
Message::OpenFile => {
if !self.is_loading {
if !self.is_dirty {
self.is_loading = true;
- Task::perform(
- Project::from_file(self.config.clone()),
- Message::FileOpened,
- )
- } else if unsaved_changes_dialog(
- "You have unsaved changes. Do you wish to discard these and open another project?",
- ) {
- self.is_dirty = false;
- self.is_loading = true;
- Task::perform(
+ return Task::perform(
Project::from_file(self.config.clone()),
Message::FileOpened,
- )
+ );
} else {
- Task::none()
+ return unsaved_changes_dialog(
+ "You have unsaved changes. Do you wish to discard these and open another project?",
+ DialogAction::OpenFile,
+ );
}
- } else {
- Task::none()
}
}
Message::FileOpened(result) => {
self.is_loading = false;
self.is_dirty = false;
- match result {
+ return match result {
Ok((path, project)) => {
self.project = project;
self.project_path = Some(path);
Task::done(Message::RefreshEditorContent)
}
- Err(error) => Task::future(error_dialog(error)).discard(),
- }
+ Err(error) => return error_dialog(error),
+ };
}
Message::SaveFile => {
if !self.is_loading {
self.is_loading = true;
- Task::perform(
+ return Task::perform(
self.project
.clone()
.write_to_file(self.project_path.clone()),
Message::FileSaved,
- )
- } else {
- Task::none()
+ );
}
}
Message::SaveFileAs => {
if !self.is_loading {
self.is_loading = true;
- Task::perform(
+ return Task::perform(
self.project.clone().write_to_file(None),
Message::FileSaved,
- )
- } else {
- Task::none()
+ );
}
}
Message::FileSaved(result) => {
@@ -341,12 +360,13 @@ impl App {
Ok(path) => {
self.project_path = Some(path);
self.is_dirty = false;
- Task::none()
}
- Err(error) => Task::future(error_dialog(error)).discard(),
+ Err(error) => return error_dialog(error),
}
}
}
+
+ Task::none()
}
fn subscription(&self) -> iced::Subscription<Message> {
@@ -405,14 +425,26 @@ impl App {
.on_click(Message::PaneClicked)
.on_drag(Message::PaneDragged);
- let content = Column::new()
+ let base = Column::new()
.push(header)
.push(pane_grid)
.spacing(5)
.align_x(Alignment::Center)
.width(Length::Fill);
- Animation::new(&self.theme, container(content).height(Length::Fill))
+ let content = Dialog::with_buttons(
+ self.dialog_is_open,
+ container(base).height(Length::Fill),
+ text(&self.dialog_content),
+ match self.dialog_buttons {
+ DialogButtons::None => vec![],
+ DialogButtons::Ok => vec![ok_button()],
+ DialogButtons::OkCancel => vec![ok_button(), cancel_button()],
+ },
+ )
+ .title(self.dialog_title);
+
+ Animation::new(&self.theme, content)
.on_update(Message::SwitchTheme)
.into()
}
diff --git a/src/types.rs b/src/types.rs
index 73728e3..a7fae1c 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -27,6 +27,10 @@ pub enum Message {
PaneResized(pane_grid::ResizeEvent),
PaneClicked(pane_grid::Pane),
PaneDragged(pane_grid::DragEvent),
+ OpenDialog(&'static str, String, DialogButtons, DialogAction),
+ CloseDialog,
+ DialogOk,
+ DialogCancel,
NewFile,
OpenFile,
FileOpened(Result<(PathBuf, Project), Error>),
@@ -35,7 +39,23 @@ pub enum Message {
FileSaved(Result<PathBuf, Error>),
}
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Copy, Default)]
+pub enum DialogButtons {
+ #[default]
+ None,
+ Ok,
+ OkCancel,
+}
+
+#[derive(Debug, Clone, Copy, Default)]
+pub enum DialogAction {
+ #[default]
+ None,
+ NewFile,
+ OpenFile,
+}
+
+#[derive(Debug, Clone, Copy)]
pub enum DesignerPane {
DesignerView,
CodeView,