Rename and reorganize several widgets (#1462)
* Rename SwatchPairInput -> WorkingColorsButton * Remove unnecessary Svelte each-loop keys * Rename (and migrate) MenuBarInput -> MenuListButton * Rename PivotAssist -> PivotInput * Rename PersistentScrollbar -> ScrollbarInput and CanvasRuler -> RulerInput * Rename DIalogModal -> Dialog * Rename WidgetRow -> WidgetSpan
This commit is contained in:
parent
e3f5e7001f
commit
719c96ecd8
|
|
@ -1,4 +1,4 @@
|
||||||
//! Handles modal dialogs that appear as floating menus in the center of the editor window.
|
//! Handles dialogs that appear as floating menus in the center of the editor window.
|
||||||
//!
|
//!
|
||||||
//! Dialogs are represented as structs that implement the `DialogLayoutHolder` trait.
|
//! Dialogs are represented as structs that implement the `DialogLayoutHolder` trait.
|
||||||
//!
|
//!
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
/// A dialog for confirming the closing of all documents viewable via `file -> close all` in the menu bar.
|
/// A dialog for confirming the closing of all documents viewable via `File -> Close All` in the menu bar.
|
||||||
pub struct CloseAllDocumentsDialog {
|
pub struct CloseAllDocumentsDialog {
|
||||||
pub unsaved_document_names: Vec<String>,
|
pub unsaved_document_names: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -196,10 +196,10 @@ impl<F: Fn(&MessageDiscriminant) -> Vec<KeysGroup>> MessageHandler<LayoutMessage
|
||||||
let callback_message = (parameter_expose_button.on_update.callback)(parameter_expose_button);
|
let callback_message = (parameter_expose_button.on_update.callback)(parameter_expose_button);
|
||||||
responses.add(callback_message);
|
responses.add(callback_message);
|
||||||
}
|
}
|
||||||
Widget::PivotAssist(pivot_assist) => {
|
Widget::PivotInput(pivot_input) => {
|
||||||
let update_value = value.as_str().expect("RadioInput update was not of type: u64");
|
let update_value = value.as_str().expect("PivotInput update was not of type: u64");
|
||||||
pivot_assist.position = update_value.into();
|
pivot_input.position = update_value.into();
|
||||||
let callback_message = (pivot_assist.on_update.callback)(pivot_assist);
|
let callback_message = (pivot_input.on_update.callback)(pivot_input);
|
||||||
responses.add(callback_message);
|
responses.add(callback_message);
|
||||||
}
|
}
|
||||||
Widget::PopoverButton(_) => {}
|
Widget::PopoverButton(_) => {}
|
||||||
|
|
@ -210,7 +210,6 @@ impl<F: Fn(&MessageDiscriminant) -> Vec<KeysGroup>> MessageHandler<LayoutMessage
|
||||||
responses.add(callback_message);
|
responses.add(callback_message);
|
||||||
}
|
}
|
||||||
Widget::Separator(_) => {}
|
Widget::Separator(_) => {}
|
||||||
Widget::SwatchPairInput(_) => {}
|
|
||||||
Widget::TextAreaInput(text_area_input) => {
|
Widget::TextAreaInput(text_area_input) => {
|
||||||
let update_value = value.as_str().expect("TextAreaInput update was not of type: string");
|
let update_value = value.as_str().expect("TextAreaInput update was not of type: string");
|
||||||
text_area_input.value = update_value.into();
|
text_area_input.value = update_value.into();
|
||||||
|
|
@ -228,6 +227,7 @@ impl<F: Fn(&MessageDiscriminant) -> Vec<KeysGroup>> MessageHandler<LayoutMessage
|
||||||
responses.add(callback_message);
|
responses.add(callback_message);
|
||||||
}
|
}
|
||||||
Widget::TextLabel(_) => {}
|
Widget::TextLabel(_) => {}
|
||||||
|
Widget::WorkingColorsButton(_) => {}
|
||||||
};
|
};
|
||||||
responses.add(ResendActiveWidget { layout_target, dirty_id: widget_id });
|
responses.add(ResendActiveWidget { layout_target, dirty_id: widget_id });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
use super::widgets::assist_widgets::*;
|
|
||||||
use super::widgets::button_widgets::*;
|
use super::widgets::button_widgets::*;
|
||||||
use super::widgets::input_widgets::*;
|
use super::widgets::input_widgets::*;
|
||||||
use super::widgets::label_widgets::*;
|
use super::widgets::label_widgets::*;
|
||||||
|
|
@ -337,7 +336,7 @@ impl LayoutGroup {
|
||||||
Widget::TextInput(x) => &mut x.tooltip,
|
Widget::TextInput(x) => &mut x.tooltip,
|
||||||
Widget::TextLabel(x) => &mut x.tooltip,
|
Widget::TextLabel(x) => &mut x.tooltip,
|
||||||
Widget::BreadcrumbTrailButtons(x) => &mut x.tooltip,
|
Widget::BreadcrumbTrailButtons(x) => &mut x.tooltip,
|
||||||
Widget::InvisibleStandinInput(_) | Widget::PivotAssist(_) | Widget::RadioInput(_) | Widget::Separator(_) | Widget::SwatchPairInput(_) => continue,
|
Widget::InvisibleStandinInput(_) | Widget::PivotInput(_) | Widget::RadioInput(_) | Widget::Separator(_) | Widget::WorkingColorsButton(_) => continue,
|
||||||
};
|
};
|
||||||
if val.is_empty() {
|
if val.is_empty() {
|
||||||
*val = tooltip.clone();
|
*val = tooltip.clone();
|
||||||
|
|
@ -483,15 +482,15 @@ pub enum Widget {
|
||||||
NumberInput(NumberInput),
|
NumberInput(NumberInput),
|
||||||
OptionalInput(OptionalInput),
|
OptionalInput(OptionalInput),
|
||||||
ParameterExposeButton(ParameterExposeButton),
|
ParameterExposeButton(ParameterExposeButton),
|
||||||
PivotAssist(PivotAssist),
|
PivotInput(PivotInput),
|
||||||
PopoverButton(PopoverButton),
|
PopoverButton(PopoverButton),
|
||||||
RadioInput(RadioInput),
|
RadioInput(RadioInput),
|
||||||
Separator(Separator),
|
Separator(Separator),
|
||||||
SwatchPairInput(SwatchPairInput),
|
|
||||||
TextAreaInput(TextAreaInput),
|
TextAreaInput(TextAreaInput),
|
||||||
TextButton(TextButton),
|
TextButton(TextButton),
|
||||||
TextInput(TextInput),
|
TextInput(TextInput),
|
||||||
TextLabel(TextLabel),
|
TextLabel(TextLabel),
|
||||||
|
WorkingColorsButton(WorkingColorsButton),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A single change to part of the UI, containing the location of the change and the new value.
|
/// A single change to part of the UI, containing the location of the change and the new value.
|
||||||
|
|
@ -559,13 +558,13 @@ impl DiffUpdate {
|
||||||
| Widget::ImageLabel(_)
|
| Widget::ImageLabel(_)
|
||||||
| Widget::CurveInput(_)
|
| Widget::CurveInput(_)
|
||||||
| Widget::InvisibleStandinInput(_)
|
| Widget::InvisibleStandinInput(_)
|
||||||
| Widget::PivotAssist(_)
|
| Widget::PivotInput(_)
|
||||||
| Widget::RadioInput(_)
|
| Widget::RadioInput(_)
|
||||||
| Widget::Separator(_)
|
| Widget::Separator(_)
|
||||||
| Widget::SwatchPairInput(_)
|
|
||||||
| Widget::TextAreaInput(_)
|
| Widget::TextAreaInput(_)
|
||||||
| Widget::TextInput(_)
|
| Widget::TextInput(_)
|
||||||
| Widget::TextLabel(_) => None,
|
| Widget::TextLabel(_)
|
||||||
|
| Widget::WorkingColorsButton(_) => None,
|
||||||
};
|
};
|
||||||
if let Some((tooltip, Some(tooltip_shortcut))) = &mut tooltip_shortcut {
|
if let Some((tooltip, Some(tooltip_shortcut))) = &mut tooltip_shortcut {
|
||||||
apply_shortcut_to_tooltip(tooltip_shortcut, tooltip);
|
apply_shortcut_to_tooltip(tooltip_shortcut, tooltip);
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ pub mod widgets;
|
||||||
|
|
||||||
pub mod widget_prelude {
|
pub mod widget_prelude {
|
||||||
pub use super::layout_widget::*;
|
pub use super::layout_widget::*;
|
||||||
pub use super::widgets::assist_widgets::*;
|
|
||||||
pub use super::widgets::button_widgets::*;
|
pub use super::widgets::button_widgets::*;
|
||||||
pub use super::widgets::input_widgets::*;
|
pub use super::widgets::input_widgets::*;
|
||||||
pub use super::widgets::label_widgets::*;
|
pub use super::widgets::label_widgets::*;
|
||||||
|
|
|
||||||
|
|
@ -1,102 +0,0 @@
|
||||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
|
||||||
use graphite_proc_macros::WidgetBuilder;
|
|
||||||
|
|
||||||
use derivative::*;
|
|
||||||
use glam::DVec2;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Clone, Default, Derivative, Serialize, Deserialize, WidgetBuilder, specta::Type)]
|
|
||||||
#[derivative(Debug, PartialEq)]
|
|
||||||
pub struct PivotAssist {
|
|
||||||
#[widget_builder(constructor)]
|
|
||||||
pub position: PivotPosition,
|
|
||||||
|
|
||||||
pub disabled: bool,
|
|
||||||
|
|
||||||
// Callbacks
|
|
||||||
#[serde(skip)]
|
|
||||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
|
||||||
pub on_update: WidgetCallback<PivotAssist>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Serialize, Deserialize, Debug, Default, PartialEq, Eq, specta::Type)]
|
|
||||||
pub enum PivotPosition {
|
|
||||||
#[default]
|
|
||||||
None,
|
|
||||||
TopLeft,
|
|
||||||
TopCenter,
|
|
||||||
TopRight,
|
|
||||||
CenterLeft,
|
|
||||||
Center,
|
|
||||||
CenterRight,
|
|
||||||
BottomLeft,
|
|
||||||
BottomCenter,
|
|
||||||
BottomRight,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&str> for PivotPosition {
|
|
||||||
fn from(input: &str) -> Self {
|
|
||||||
match input {
|
|
||||||
"None" => PivotPosition::None,
|
|
||||||
"TopLeft" => PivotPosition::TopLeft,
|
|
||||||
"TopCenter" => PivotPosition::TopCenter,
|
|
||||||
"TopRight" => PivotPosition::TopRight,
|
|
||||||
"CenterLeft" => PivotPosition::CenterLeft,
|
|
||||||
"Center" => PivotPosition::Center,
|
|
||||||
"CenterRight" => PivotPosition::CenterRight,
|
|
||||||
"BottomLeft" => PivotPosition::BottomLeft,
|
|
||||||
"BottomCenter" => PivotPosition::BottomCenter,
|
|
||||||
"BottomRight" => PivotPosition::BottomRight,
|
|
||||||
_ => panic!("Failed parsing unrecognized PivotPosition enum value '{input}'"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<PivotPosition> for Option<DVec2> {
|
|
||||||
fn from(input: PivotPosition) -> Self {
|
|
||||||
match input {
|
|
||||||
PivotPosition::None => None,
|
|
||||||
PivotPosition::TopLeft => Some(DVec2::new(0., 0.)),
|
|
||||||
PivotPosition::TopCenter => Some(DVec2::new(0.5, 0.)),
|
|
||||||
PivotPosition::TopRight => Some(DVec2::new(1., 0.)),
|
|
||||||
PivotPosition::CenterLeft => Some(DVec2::new(0., 0.5)),
|
|
||||||
PivotPosition::Center => Some(DVec2::new(0.5, 0.5)),
|
|
||||||
PivotPosition::CenterRight => Some(DVec2::new(1., 0.5)),
|
|
||||||
PivotPosition::BottomLeft => Some(DVec2::new(0., 1.)),
|
|
||||||
PivotPosition::BottomCenter => Some(DVec2::new(0.5, 1.)),
|
|
||||||
PivotPosition::BottomRight => Some(DVec2::new(1., 1.)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<DVec2> for PivotPosition {
|
|
||||||
fn from(input: DVec2) -> Self {
|
|
||||||
const TOLERANCE: f64 = 1e-5_f64;
|
|
||||||
if input.y.abs() < TOLERANCE {
|
|
||||||
if input.x.abs() < TOLERANCE {
|
|
||||||
return PivotPosition::TopLeft;
|
|
||||||
} else if (input.x - 0.5).abs() < TOLERANCE {
|
|
||||||
return PivotPosition::TopCenter;
|
|
||||||
} else if (input.x - 1.).abs() < TOLERANCE {
|
|
||||||
return PivotPosition::TopRight;
|
|
||||||
}
|
|
||||||
} else if (input.y - 0.5).abs() < TOLERANCE {
|
|
||||||
if input.x.abs() < TOLERANCE {
|
|
||||||
return PivotPosition::CenterLeft;
|
|
||||||
} else if (input.x - 0.5).abs() < TOLERANCE {
|
|
||||||
return PivotPosition::Center;
|
|
||||||
} else if (input.x - 1.).abs() < TOLERANCE {
|
|
||||||
return PivotPosition::CenterRight;
|
|
||||||
}
|
|
||||||
} else if (input.y - 1.).abs() < TOLERANCE {
|
|
||||||
if input.x.abs() < TOLERANCE {
|
|
||||||
return PivotPosition::BottomLeft;
|
|
||||||
} else if (input.x - 0.5).abs() < TOLERANCE {
|
|
||||||
return PivotPosition::BottomCenter;
|
|
||||||
} else if (input.x - 1.).abs() < TOLERANCE {
|
|
||||||
return PivotPosition::BottomRight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PivotPosition::None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -106,6 +106,16 @@ pub struct TextButton {
|
||||||
pub on_update: WidgetCallback<TextButton>,
|
pub on_update: WidgetCallback<TextButton>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize, Derivative, WidgetBuilder, specta::Type)]
|
||||||
|
#[derivative(Debug, PartialEq, Default)]
|
||||||
|
pub struct WorkingColorsButton {
|
||||||
|
#[widget_builder(constructor)]
|
||||||
|
pub primary: Color,
|
||||||
|
|
||||||
|
#[widget_builder(constructor)]
|
||||||
|
pub secondary: Color,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Derivative, Serialize, Deserialize, WidgetBuilder, specta::Type)]
|
#[derive(Clone, Derivative, Serialize, Deserialize, WidgetBuilder, specta::Type)]
|
||||||
#[derivative(Debug, PartialEq, Default)]
|
#[derivative(Debug, PartialEq, Default)]
|
||||||
pub struct ColorButton {
|
pub struct ColorButton {
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@ use crate::messages::layout::utility_types::widget_prelude::*;
|
||||||
|
|
||||||
use document_legacy::layers::layer_info::LayerDataTypeDiscriminant;
|
use document_legacy::layers::layer_info::LayerDataTypeDiscriminant;
|
||||||
use document_legacy::LayerId;
|
use document_legacy::LayerId;
|
||||||
use graphene_core::raster::color::Color;
|
|
||||||
use graphene_core::raster::curve::Curve;
|
use graphene_core::raster::curve::Curve;
|
||||||
use graphite_proc_macros::WidgetBuilder;
|
use graphite_proc_macros::WidgetBuilder;
|
||||||
|
|
||||||
use derivative::*;
|
use derivative::*;
|
||||||
|
use glam::DVec2;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Clone, Derivative, Serialize, Deserialize, WidgetBuilder, specta::Type)]
|
#[derive(Clone, Derivative, Serialize, Deserialize, WidgetBuilder, specta::Type)]
|
||||||
|
|
@ -339,16 +339,6 @@ pub struct RadioEntryData {
|
||||||
pub on_update: WidgetCallback<()>,
|
pub on_update: WidgetCallback<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Derivative, WidgetBuilder, specta::Type)]
|
|
||||||
#[derivative(Debug, PartialEq, Default)]
|
|
||||||
pub struct SwatchPairInput {
|
|
||||||
#[widget_builder(constructor)]
|
|
||||||
pub primary: Color,
|
|
||||||
|
|
||||||
#[widget_builder(constructor)]
|
|
||||||
pub secondary: Color,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Derivative, WidgetBuilder, specta::Type)]
|
#[derive(Clone, Serialize, Deserialize, Derivative, WidgetBuilder, specta::Type)]
|
||||||
#[derivative(Debug, PartialEq, Default)]
|
#[derivative(Debug, PartialEq, Default)]
|
||||||
pub struct TextAreaInput {
|
pub struct TextAreaInput {
|
||||||
|
|
@ -405,3 +395,99 @@ pub struct CurveInput {
|
||||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||||
pub on_update: WidgetCallback<CurveInput>,
|
pub on_update: WidgetCallback<CurveInput>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default, Derivative, Serialize, Deserialize, WidgetBuilder, specta::Type)]
|
||||||
|
#[derivative(Debug, PartialEq)]
|
||||||
|
pub struct PivotInput {
|
||||||
|
#[widget_builder(constructor)]
|
||||||
|
pub position: PivotPosition,
|
||||||
|
|
||||||
|
pub disabled: bool,
|
||||||
|
|
||||||
|
// Callbacks
|
||||||
|
#[serde(skip)]
|
||||||
|
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||||
|
pub on_update: WidgetCallback<PivotInput>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Serialize, Deserialize, Debug, Default, PartialEq, Eq, specta::Type)]
|
||||||
|
pub enum PivotPosition {
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
TopLeft,
|
||||||
|
TopCenter,
|
||||||
|
TopRight,
|
||||||
|
CenterLeft,
|
||||||
|
Center,
|
||||||
|
CenterRight,
|
||||||
|
BottomLeft,
|
||||||
|
BottomCenter,
|
||||||
|
BottomRight,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for PivotPosition {
|
||||||
|
fn from(input: &str) -> Self {
|
||||||
|
match input {
|
||||||
|
"None" => PivotPosition::None,
|
||||||
|
"TopLeft" => PivotPosition::TopLeft,
|
||||||
|
"TopCenter" => PivotPosition::TopCenter,
|
||||||
|
"TopRight" => PivotPosition::TopRight,
|
||||||
|
"CenterLeft" => PivotPosition::CenterLeft,
|
||||||
|
"Center" => PivotPosition::Center,
|
||||||
|
"CenterRight" => PivotPosition::CenterRight,
|
||||||
|
"BottomLeft" => PivotPosition::BottomLeft,
|
||||||
|
"BottomCenter" => PivotPosition::BottomCenter,
|
||||||
|
"BottomRight" => PivotPosition::BottomRight,
|
||||||
|
_ => panic!("Failed parsing unrecognized PivotPosition enum value '{input}'"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PivotPosition> for Option<DVec2> {
|
||||||
|
fn from(input: PivotPosition) -> Self {
|
||||||
|
match input {
|
||||||
|
PivotPosition::None => None,
|
||||||
|
PivotPosition::TopLeft => Some(DVec2::new(0., 0.)),
|
||||||
|
PivotPosition::TopCenter => Some(DVec2::new(0.5, 0.)),
|
||||||
|
PivotPosition::TopRight => Some(DVec2::new(1., 0.)),
|
||||||
|
PivotPosition::CenterLeft => Some(DVec2::new(0., 0.5)),
|
||||||
|
PivotPosition::Center => Some(DVec2::new(0.5, 0.5)),
|
||||||
|
PivotPosition::CenterRight => Some(DVec2::new(1., 0.5)),
|
||||||
|
PivotPosition::BottomLeft => Some(DVec2::new(0., 1.)),
|
||||||
|
PivotPosition::BottomCenter => Some(DVec2::new(0.5, 1.)),
|
||||||
|
PivotPosition::BottomRight => Some(DVec2::new(1., 1.)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DVec2> for PivotPosition {
|
||||||
|
fn from(input: DVec2) -> Self {
|
||||||
|
const TOLERANCE: f64 = 1e-5_f64;
|
||||||
|
if input.y.abs() < TOLERANCE {
|
||||||
|
if input.x.abs() < TOLERANCE {
|
||||||
|
return PivotPosition::TopLeft;
|
||||||
|
} else if (input.x - 0.5).abs() < TOLERANCE {
|
||||||
|
return PivotPosition::TopCenter;
|
||||||
|
} else if (input.x - 1.).abs() < TOLERANCE {
|
||||||
|
return PivotPosition::TopRight;
|
||||||
|
}
|
||||||
|
} else if (input.y - 0.5).abs() < TOLERANCE {
|
||||||
|
if input.x.abs() < TOLERANCE {
|
||||||
|
return PivotPosition::CenterLeft;
|
||||||
|
} else if (input.x - 0.5).abs() < TOLERANCE {
|
||||||
|
return PivotPosition::Center;
|
||||||
|
} else if (input.x - 1.).abs() < TOLERANCE {
|
||||||
|
return PivotPosition::CenterRight;
|
||||||
|
}
|
||||||
|
} else if (input.y - 1.).abs() < TOLERANCE {
|
||||||
|
if input.x.abs() < TOLERANCE {
|
||||||
|
return PivotPosition::BottomLeft;
|
||||||
|
} else if (input.x - 0.5).abs() < TOLERANCE {
|
||||||
|
return PivotPosition::BottomCenter;
|
||||||
|
} else if (input.x - 1.).abs() < TOLERANCE {
|
||||||
|
return PivotPosition::BottomRight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PivotPosition::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
pub mod assist_widgets;
|
|
||||||
pub mod button_widgets;
|
pub mod button_widgets;
|
||||||
pub mod input_widgets;
|
pub mod input_widgets;
|
||||||
pub mod label_widgets;
|
pub mod label_widgets;
|
||||||
|
|
|
||||||
|
|
@ -1181,8 +1181,8 @@ pub fn transform_properties(document_node: &DocumentNode, node_id: NodeId, _cont
|
||||||
{
|
{
|
||||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||||
widgets.push(
|
widgets.push(
|
||||||
PivotAssist::new(pivot.into())
|
PivotInput::new(pivot.into())
|
||||||
.on_update(update_value(|pivot: &PivotAssist| TaggedValue::DVec2(Into::<Option<DVec2>>::into(pivot.position).unwrap()), node_id, 5))
|
.on_update(update_value(|pivot: &PivotInput| TaggedValue::DVec2(Into::<Option<DVec2>>::into(pivot.position).unwrap()), node_id, 5))
|
||||||
.widget_holder(),
|
.widget_holder(),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -167,8 +167,8 @@ fn node_section_transform(layer: &Layer, persistent_data: &PersistentData) -> La
|
||||||
widgets: vec![
|
widgets: vec![
|
||||||
TextLabel::new("Location").widget_holder(),
|
TextLabel::new("Location").widget_holder(),
|
||||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||||
PivotAssist::new(layer.pivot.into())
|
PivotInput::new(layer.pivot.into())
|
||||||
.on_update(|pivot_assist: &PivotAssist| PropertiesPanelMessage::SetPivot { new_position: pivot_assist.position }.into())
|
.on_update(|pivot_input: &PivotInput| PropertiesPanelMessage::SetPivot { new_position: pivot_input.position }.into())
|
||||||
.widget_holder(),
|
.widget_holder(),
|
||||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||||
NumberInput::new(Some(layer.transform.x() + pivot.x))
|
NumberInput::new(Some(layer.transform.x() + pivot.x))
|
||||||
|
|
|
||||||
|
|
@ -116,8 +116,8 @@ impl SelectTool {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
fn pivot_widget(&self, disabled: bool) -> WidgetHolder {
|
fn pivot_widget(&self, disabled: bool) -> WidgetHolder {
|
||||||
PivotAssist::new(self.tool_data.pivot.to_pivot_position())
|
PivotInput::new(self.tool_data.pivot.to_pivot_position())
|
||||||
.on_update(|pivot_assist: &PivotAssist| SelectToolMessage::SetPivot { position: pivot_assist.position }.into())
|
.on_update(|pivot_input: &PivotInput| SelectToolMessage::SetPivot { position: pivot_input.position }.into())
|
||||||
.disabled(disabled)
|
.disabled(disabled)
|
||||||
.widget_holder()
|
.widget_holder()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,7 @@ impl DocumentToolData {
|
||||||
pub fn update_working_colors(&self, responses: &mut VecDeque<Message>) {
|
pub fn update_working_colors(&self, responses: &mut VecDeque<Message>) {
|
||||||
let layout = WidgetLayout::new(vec![
|
let layout = WidgetLayout::new(vec![
|
||||||
LayoutGroup::Row {
|
LayoutGroup::Row {
|
||||||
widgets: vec![SwatchPairInput::new(self.primary_color, self.secondary_color).widget_holder()],
|
widgets: vec![WorkingColorsButton::new(self.primary_color, self.secondary_color).widget_holder()],
|
||||||
},
|
},
|
||||||
LayoutGroup::Row {
|
LayoutGroup::Row {
|
||||||
widgets: vec![
|
widgets: vec![
|
||||||
|
|
|
||||||
|
|
@ -272,10 +272,10 @@
|
||||||
.popover-button,
|
.popover-button,
|
||||||
.color-button > button,
|
.color-button > button,
|
||||||
.color-picker .preset-color,
|
.color-picker .preset-color,
|
||||||
.swatch-pair .swatch > button,
|
.working-colors-button .swatch > button,
|
||||||
.radio-input button,
|
.radio-input button,
|
||||||
.menu-list,
|
.menu-list,
|
||||||
.menu-bar-input .entry,
|
.menu-list-button .entry,
|
||||||
.layer-tree .expand-arrow,
|
.layer-tree .expand-arrow,
|
||||||
.widget-section .header {
|
.widget-section .header {
|
||||||
&:focus-visible {
|
&:focus-visible {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Each component represents a (usually reusable) part of the Graphite editor GUI.
|
||||||
|
|
||||||
## Floating Menus: `floating-menus/`
|
## Floating Menus: `floating-menus/`
|
||||||
|
|
||||||
The temporary UI areas with dark backgrounds which hover over the top of the editor window content. Examples include popovers, dropdown menu selectors, and dialog modals.
|
The temporary UI areas with dark backgrounds which hover over the top of the editor window content. Examples include menu lists, popovers, and dialogs.
|
||||||
|
|
||||||
## Layout: `layout/`
|
## Layout: `layout/`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -319,7 +319,7 @@
|
||||||
<TextLabel tooltip="Red/Green/Blue channels of the color, integers 0–255">RGB</TextLabel>
|
<TextLabel tooltip="Red/Green/Blue channels of the color, integers 0–255">RGB</TextLabel>
|
||||||
<Separator />
|
<Separator />
|
||||||
<LayoutRow>
|
<LayoutRow>
|
||||||
{#each rgbChannels as [channel, strength], index (channel)}
|
{#each rgbChannels as [channel, strength], index}
|
||||||
{#if index > 0}
|
{#if index > 0}
|
||||||
<Separator type="Related" />
|
<Separator type="Related" />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
@ -343,7 +343,7 @@
|
||||||
>
|
>
|
||||||
<Separator />
|
<Separator />
|
||||||
<LayoutRow>
|
<LayoutRow>
|
||||||
{#each hsvChannels as [channel, strength], index (channel)}
|
{#each hsvChannels as [channel, strength], index}
|
||||||
{#if index > 0}
|
{#if index > 0}
|
||||||
<Separator type="Related" />
|
<Separator type="Related" />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- TODO: Use https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog for improved accessibility -->
|
<!-- TODO: Use https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog for improved accessibility -->
|
||||||
<FloatingMenu open={true} class="dialog-modal" type="Dialog" direction="Center" bind:this={self} data-dialog-modal>
|
<FloatingMenu open={true} class="dialog" type="Dialog" direction="Center" bind:this={self} data-dialog>
|
||||||
<LayoutRow class="header-area">
|
<LayoutRow class="header-area">
|
||||||
<!-- `$dialog.icon` class exists to provide special sizing in CSS to specific icons -->
|
<!-- `$dialog.icon` class exists to provide special sizing in CSS to specific icons -->
|
||||||
<IconLabel icon={$dialog.icon} class={$dialog.icon.toLowerCase()} />
|
<IconLabel icon={$dialog.icon} class={$dialog.icon.toLowerCase()} />
|
||||||
|
|
@ -39,11 +39,11 @@
|
||||||
{/if}
|
{/if}
|
||||||
{#if $dialog.panicDetails}
|
{#if $dialog.panicDetails}
|
||||||
<div class="widget-layout details">
|
<div class="widget-layout details">
|
||||||
<div class="widget-row"><TextLabel bold={true}>The editor crashed — sorry about that</TextLabel></div>
|
<div class="widget-span row"><TextLabel bold={true}>The editor crashed — sorry about that</TextLabel></div>
|
||||||
<div class="widget-row"><TextLabel>Please report this by filing an issue on GitHub:</TextLabel></div>
|
<div class="widget-span row"><TextLabel>Please report this by filing an issue on GitHub:</TextLabel></div>
|
||||||
<div class="widget-row"><TextButton label="Report Bug" icon="Warning" noBackground={true} action={() => window.open(githubUrl($dialog.panicDetails), "_blank")} /></div>
|
<div class="widget-span row"><TextButton label="Report Bug" icon="Warning" noBackground={true} action={() => window.open(githubUrl($dialog.panicDetails), "_blank")} /></div>
|
||||||
<div class="widget-row"><TextLabel multiline={true}>Reload the editor to continue. If this occurs<br />immediately on repeated reloads, clear storage:</TextLabel></div>
|
<div class="widget-span row"><TextLabel multiline={true}>Reload the editor to continue. If this occurs<br />immediately on repeated reloads, clear storage:</TextLabel></div>
|
||||||
<div class="widget-row">
|
<div class="widget-span row">
|
||||||
<TextButton
|
<TextButton
|
||||||
label="Clear Saved Documents"
|
label="Clear Saved Documents"
|
||||||
icon="Trash"
|
icon="Trash"
|
||||||
|
|
@ -75,7 +75,7 @@
|
||||||
</FloatingMenu>
|
</FloatingMenu>
|
||||||
|
|
||||||
<style lang="scss" global>
|
<style lang="scss" global>
|
||||||
.dialog-modal {
|
.dialog {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
@ -103,7 +103,7 @@
|
||||||
|
|
||||||
const menuOpen = open;
|
const menuOpen = open;
|
||||||
const flatEntries = entries.flat().filter((entry) => !entry.disabled);
|
const flatEntries = entries.flat().filter((entry) => !entry.disabled);
|
||||||
const openChild = flatEntries.findIndex((entry) => entry.children?.length && entry.ref?.open);
|
const openChild = flatEntries.findIndex((entry) => (entry.children?.length ?? 0) > 0 && entry.ref?.open);
|
||||||
|
|
||||||
const openSubmenu = (highlightedEntry: MenuListEntry) => {
|
const openSubmenu = (highlightedEntry: MenuListEntry) => {
|
||||||
if (highlightedEntry.ref && highlightedEntry.children?.length) {
|
if (highlightedEntry.ref && highlightedEntry.children?.length) {
|
||||||
|
|
|
||||||
|
|
@ -27,12 +27,12 @@
|
||||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||||
import Graph from "@graphite/components/views/Graph.svelte";
|
import Graph from "@graphite/components/views/Graph.svelte";
|
||||||
import CanvasRuler from "@graphite/components/widgets/metrics/CanvasRuler.svelte";
|
import RulerInput from "@graphite/components/widgets/inputs/RulerInput.svelte";
|
||||||
import PersistentScrollbar from "@graphite/components/widgets/metrics/PersistentScrollbar.svelte";
|
import ScrollbarInput from "@graphite/components/widgets/inputs/ScrollbarInput.svelte";
|
||||||
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
||||||
|
|
||||||
let rulerHorizontal: CanvasRuler | undefined;
|
let rulerHorizontal: RulerInput | undefined;
|
||||||
let rulerVertical: CanvasRuler | undefined;
|
let rulerVertical: RulerInput | undefined;
|
||||||
let viewport: HTMLDivElement | undefined;
|
let viewport: HTMLDivElement | undefined;
|
||||||
|
|
||||||
const editor = getContext<Editor>("editor");
|
const editor = getContext<Editor>("editor");
|
||||||
|
|
@ -441,11 +441,11 @@
|
||||||
</LayoutCol>
|
</LayoutCol>
|
||||||
<LayoutCol class="table">
|
<LayoutCol class="table">
|
||||||
<LayoutRow class="ruler-or-scrollbar top-ruler">
|
<LayoutRow class="ruler-or-scrollbar top-ruler">
|
||||||
<CanvasRuler origin={rulerOrigin.x} majorMarkSpacing={rulerSpacing} numberInterval={rulerInterval} direction="Horizontal" bind:this={rulerHorizontal} />
|
<RulerInput origin={rulerOrigin.x} majorMarkSpacing={rulerSpacing} numberInterval={rulerInterval} direction="Horizontal" bind:this={rulerHorizontal} />
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
<LayoutRow class="viewport-container">
|
<LayoutRow class="viewport-container">
|
||||||
<LayoutCol class="ruler-or-scrollbar">
|
<LayoutCol class="ruler-or-scrollbar">
|
||||||
<CanvasRuler origin={rulerOrigin.y} majorMarkSpacing={rulerSpacing} numberInterval={rulerInterval} direction="Vertical" bind:this={rulerVertical} />
|
<RulerInput origin={rulerOrigin.y} majorMarkSpacing={rulerSpacing} numberInterval={rulerInterval} direction="Vertical" bind:this={rulerVertical} />
|
||||||
</LayoutCol>
|
</LayoutCol>
|
||||||
<LayoutCol class="viewport-container" styles={{ cursor: canvasCursor }}>
|
<LayoutCol class="viewport-container" styles={{ cursor: canvasCursor }}>
|
||||||
{#if cursorEyedropper}
|
{#if cursorEyedropper}
|
||||||
|
|
@ -482,7 +482,7 @@
|
||||||
</div>
|
</div>
|
||||||
</LayoutCol>
|
</LayoutCol>
|
||||||
<LayoutCol class="ruler-or-scrollbar right-scrollbar">
|
<LayoutCol class="ruler-or-scrollbar right-scrollbar">
|
||||||
<PersistentScrollbar
|
<ScrollbarInput
|
||||||
direction="Vertical"
|
direction="Vertical"
|
||||||
handleLength={scrollbarSize.y}
|
handleLength={scrollbarSize.y}
|
||||||
handlePosition={scrollbarPos.y}
|
handlePosition={scrollbarPos.y}
|
||||||
|
|
@ -492,7 +492,7 @@
|
||||||
</LayoutCol>
|
</LayoutCol>
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
<LayoutRow class="ruler-or-scrollbar bottom-scrollbar">
|
<LayoutRow class="ruler-or-scrollbar bottom-scrollbar">
|
||||||
<PersistentScrollbar
|
<ScrollbarInput
|
||||||
direction="Horizontal"
|
direction="Horizontal"
|
||||||
handleLength={scrollbarSize.x}
|
handleLength={scrollbarSize.x}
|
||||||
handlePosition={scrollbarPos.x}
|
handlePosition={scrollbarPos.x}
|
||||||
|
|
@ -572,10 +572,10 @@
|
||||||
.widget-layout:last-of-type {
|
.widget-layout:last-of-type {
|
||||||
height: auto;
|
height: auto;
|
||||||
|
|
||||||
.widget-row {
|
.widget-span.row {
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
|
||||||
.swatch-pair {
|
.working-colors-button {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -594,16 +594,16 @@
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-ruler .canvas-ruler {
|
.top-ruler .ruler-input {
|
||||||
padding-left: 16px;
|
padding-left: 16px;
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.right-scrollbar .persistent-scrollbar {
|
.right-scrollbar .scrollbar-input {
|
||||||
margin-top: -16px;
|
margin-top: -16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-scrollbar .persistent-scrollbar {
|
.bottom-scrollbar .scrollbar-input {
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { isWidgetColumn, isWidgetRow, isWidgetSection, type WidgetLayout } from "@graphite/wasm-communication/messages";
|
import { isWidgetSpanColumn, isWidgetSpanRow, isWidgetSection, type WidgetLayout } from "@graphite/wasm-communication/messages";
|
||||||
|
|
||||||
import WidgetSection from "@graphite/components/widgets/groups/WidgetSection.svelte";
|
import WidgetSection from "@graphite/components/widgets/WidgetSection.svelte";
|
||||||
import WidgetRow from "@graphite/components/widgets/WidgetRow.svelte";
|
import WidgetSpan from "@graphite/components/widgets/WidgetSpan.svelte";
|
||||||
|
|
||||||
export let layout: WidgetLayout;
|
export let layout: WidgetLayout;
|
||||||
let className = "";
|
let className = "";
|
||||||
|
|
@ -14,15 +14,14 @@
|
||||||
.join(" ");
|
.join(" ");
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- TODO: Refactor this component (together with `WidgetRow.svelte`) to be more logically consistent with our layout definition goals, in terms of naming and capabilities -->
|
|
||||||
<div class={`widget-layout ${className} ${extraClasses}`.trim()}>
|
<div class={`widget-layout ${className} ${extraClasses}`.trim()}>
|
||||||
{#each layout.layout as layoutGroup, index (index)}
|
{#each layout.layout as layoutGroup}
|
||||||
{#if isWidgetColumn(layoutGroup) || isWidgetRow(layoutGroup)}
|
{#if isWidgetSpanRow(layoutGroup) || isWidgetSpanColumn(layoutGroup)}
|
||||||
<WidgetRow widgetData={layoutGroup} layoutTarget={layout.layoutTarget} />
|
<WidgetSpan widgetData={layoutGroup} layoutTarget={layout.layoutTarget} />
|
||||||
{:else if isWidgetSection(layoutGroup)}
|
{:else if isWidgetSection(layoutGroup)}
|
||||||
<WidgetSection widgetData={layoutGroup} layoutTarget={layout.layoutTarget} />
|
<WidgetSection widgetData={layoutGroup} layoutTarget={layout.layoutTarget} />
|
||||||
{:else}
|
{:else}
|
||||||
<span style="color: #d6536e">Error: The widget row that belongs here has an invalid layout group type</span>
|
<span style="color: #d6536e">Error: The widget layout that belongs here has an invalid layout group type</span>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { isWidgetRow, isWidgetSection, type WidgetSection as WidgetSectionFromJsMessages } from "@graphite/wasm-communication/messages";
|
import { isWidgetSpanRow, isWidgetSpanColumn, isWidgetSection, type WidgetSection as WidgetSectionFromJsMessages } from "@graphite/wasm-communication/messages";
|
||||||
|
|
||||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||||
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
||||||
import WidgetRow from "@graphite/components/widgets/WidgetRow.svelte";
|
import WidgetSpan from "@graphite/components/widgets/WidgetSpan.svelte";
|
||||||
|
|
||||||
export let widgetData: WidgetSectionFromJsMessages;
|
export let widgetData: WidgetSectionFromJsMessages;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
|
@ -20,9 +20,11 @@
|
||||||
</button>
|
</button>
|
||||||
{#if expanded}
|
{#if expanded}
|
||||||
<LayoutCol class="body">
|
<LayoutCol class="body">
|
||||||
{#each widgetData.layout as layoutGroup, index (index)}
|
{#each widgetData.layout as layoutGroup}
|
||||||
{#if isWidgetRow(layoutGroup)}
|
{#if isWidgetSpanRow(layoutGroup)}
|
||||||
<WidgetRow widgetData={layoutGroup} {layoutTarget} />
|
<WidgetSpan widgetData={layoutGroup} {layoutTarget} />
|
||||||
|
{:else if isWidgetSpanColumn(layoutGroup)}
|
||||||
|
<span style="color: #d6536e">Error: The WidgetSpan used here should be a row not a column</span>
|
||||||
{:else if isWidgetSection(layoutGroup)}
|
{:else if isWidgetSection(layoutGroup)}
|
||||||
<svelte:self widgetData={layoutGroup} {layoutTarget} />
|
<svelte:self widgetData={layoutGroup} {layoutTarget} />
|
||||||
{:else}
|
{:else}
|
||||||
|
|
@ -105,7 +107,7 @@
|
||||||
border-radius: 0 0 4px 4px;
|
border-radius: 0 0 4px 4px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.widget-row {
|
.widget-span.row {
|
||||||
&:first-child {
|
&:first-child {
|
||||||
margin-top: calc(4px - 1px);
|
margin-top: calc(4px - 1px);
|
||||||
}
|
}
|
||||||
|
|
@ -1,19 +1,18 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext } from "svelte";
|
import { getContext } from "svelte";
|
||||||
|
|
||||||
import ColorButton from "./buttons/ColorButton.svelte";
|
|
||||||
|
|
||||||
import { debouncer } from "@graphite/utility-functions/debounce";
|
import { debouncer } from "@graphite/utility-functions/debounce";
|
||||||
import type { Editor } from "@graphite/wasm-communication/editor";
|
import type { Editor } from "@graphite/wasm-communication/editor";
|
||||||
import type { Widget, WidgetColumn, WidgetRow } from "@graphite/wasm-communication/messages";
|
import type { Widget, WidgetSpanColumn, WidgetSpanRow } from "@graphite/wasm-communication/messages";
|
||||||
import { narrowWidgetProps, isWidgetColumn, isWidgetRow } from "@graphite/wasm-communication/messages";
|
import { narrowWidgetProps, isWidgetSpanColumn, isWidgetSpanRow } from "@graphite/wasm-communication/messages";
|
||||||
|
|
||||||
import PivotAssist from "@graphite/components/widgets/assists/PivotAssist.svelte";
|
|
||||||
import BreadcrumbTrailButtons from "@graphite/components/widgets/buttons/BreadcrumbTrailButtons.svelte";
|
import BreadcrumbTrailButtons from "@graphite/components/widgets/buttons/BreadcrumbTrailButtons.svelte";
|
||||||
|
import ColorButton from "@graphite/components/widgets/buttons/ColorButton.svelte";
|
||||||
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
|
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
|
||||||
import ParameterExposeButton from "@graphite/components/widgets/buttons/ParameterExposeButton.svelte";
|
import ParameterExposeButton from "@graphite/components/widgets/buttons/ParameterExposeButton.svelte";
|
||||||
import PopoverButton from "@graphite/components/widgets/buttons/PopoverButton.svelte";
|
import PopoverButton from "@graphite/components/widgets/buttons/PopoverButton.svelte";
|
||||||
import TextButton from "@graphite/components/widgets/buttons/TextButton.svelte";
|
import TextButton from "@graphite/components/widgets/buttons/TextButton.svelte";
|
||||||
|
import WorkingColorsButton from "@graphite/components/widgets/buttons/WorkingColorsButton.svelte";
|
||||||
import CheckboxInput from "@graphite/components/widgets/inputs/CheckboxInput.svelte";
|
import CheckboxInput from "@graphite/components/widgets/inputs/CheckboxInput.svelte";
|
||||||
import CurveInput from "@graphite/components/widgets/inputs/CurveInput.svelte";
|
import CurveInput from "@graphite/components/widgets/inputs/CurveInput.svelte";
|
||||||
import DropdownInput from "@graphite/components/widgets/inputs/DropdownInput.svelte";
|
import DropdownInput from "@graphite/components/widgets/inputs/DropdownInput.svelte";
|
||||||
|
|
@ -21,8 +20,8 @@
|
||||||
import LayerReferenceInput from "@graphite/components/widgets/inputs/LayerReferenceInput.svelte";
|
import LayerReferenceInput from "@graphite/components/widgets/inputs/LayerReferenceInput.svelte";
|
||||||
import NumberInput from "@graphite/components/widgets/inputs/NumberInput.svelte";
|
import NumberInput from "@graphite/components/widgets/inputs/NumberInput.svelte";
|
||||||
import OptionalInput from "@graphite/components/widgets/inputs/OptionalInput.svelte";
|
import OptionalInput from "@graphite/components/widgets/inputs/OptionalInput.svelte";
|
||||||
|
import PivotInput from "@graphite/components/widgets/inputs/PivotInput.svelte";
|
||||||
import RadioInput from "@graphite/components/widgets/inputs/RadioInput.svelte";
|
import RadioInput from "@graphite/components/widgets/inputs/RadioInput.svelte";
|
||||||
import SwatchPairInput from "@graphite/components/widgets/inputs/SwatchPairInput.svelte";
|
|
||||||
import TextAreaInput from "@graphite/components/widgets/inputs/TextAreaInput.svelte";
|
import TextAreaInput from "@graphite/components/widgets/inputs/TextAreaInput.svelte";
|
||||||
import TextInput from "@graphite/components/widgets/inputs/TextInput.svelte";
|
import TextInput from "@graphite/components/widgets/inputs/TextInput.svelte";
|
||||||
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
||||||
|
|
@ -35,7 +34,7 @@
|
||||||
|
|
||||||
const editor = getContext<Editor>("editor");
|
const editor = getContext<Editor>("editor");
|
||||||
|
|
||||||
export let widgetData: WidgetColumn | WidgetRow;
|
export let widgetData: WidgetSpanRow | WidgetSpanColumn;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export let layoutTarget: any;
|
export let layoutTarget: any;
|
||||||
|
|
||||||
|
|
@ -43,16 +42,15 @@
|
||||||
$: widgets = watchWidgets(widgetData);
|
$: widgets = watchWidgets(widgetData);
|
||||||
$: widgetsAndNextSiblingIsSuffix = watchWidgetsAndNextSiblingIsSuffix(widgets);
|
$: widgetsAndNextSiblingIsSuffix = watchWidgetsAndNextSiblingIsSuffix(widgets);
|
||||||
|
|
||||||
function watchDirection(widgetData: WidgetRow | WidgetColumn): "row" | "column" | "ERROR" {
|
function watchDirection(widgetData: WidgetSpanRow | WidgetSpanColumn): "row" | "column" | undefined {
|
||||||
if (isWidgetRow(widgetData)) return "row";
|
if (isWidgetSpanRow(widgetData)) return "row";
|
||||||
if (isWidgetColumn(widgetData)) return "column";
|
if (isWidgetSpanColumn(widgetData)) return "column";
|
||||||
return "ERROR";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function watchWidgets(widgetData: WidgetRow | WidgetColumn): Widget[] {
|
function watchWidgets(widgetData: WidgetSpanRow | WidgetSpanColumn): Widget[] {
|
||||||
let widgets: Widget[] = [];
|
let widgets: Widget[] = [];
|
||||||
if (isWidgetRow(widgetData)) widgets = widgetData.rowWidgets;
|
if (isWidgetSpanRow(widgetData)) widgets = widgetData.rowWidgets;
|
||||||
else if (isWidgetColumn(widgetData)) widgets = widgetData.columnWidgets;
|
else if (isWidgetSpanColumn(widgetData)) widgets = widgetData.columnWidgets;
|
||||||
return widgets;
|
return widgets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,8 +81,8 @@
|
||||||
<!-- TODO: Refactor this component to use `<svelte:component this={attributesObject} />` to avoid all the separate conditional components -->
|
<!-- TODO: Refactor this component to use `<svelte:component this={attributesObject} />` to avoid all the separate conditional components -->
|
||||||
<!-- TODO: Also rename this component, and probably move the `widget-${direction}` wrapper to be part of `WidgetLayout.svelte` as part of its refactor -->
|
<!-- TODO: Also rename this component, and probably move the `widget-${direction}` wrapper to be part of `WidgetLayout.svelte` as part of its refactor -->
|
||||||
|
|
||||||
<div class={`widget-${direction}`}>
|
<div class="widget-span" class:row={direction === "row"} class:column={direction === "column"}>
|
||||||
{#each widgetsAndNextSiblingIsSuffix as [component, nextIsSuffix], index (index)}
|
{#each widgetsAndNextSiblingIsSuffix as [component, nextIsSuffix], index}
|
||||||
{@const checkboxInput = narrowWidgetProps(component.props, "CheckboxInput")}
|
{@const checkboxInput = narrowWidgetProps(component.props, "CheckboxInput")}
|
||||||
{#if checkboxInput}
|
{#if checkboxInput}
|
||||||
<CheckboxInput {...exclude(checkboxInput)} on:checked={({ detail }) => updateLayout(index, detail)} />
|
<CheckboxInput {...exclude(checkboxInput)} on:checked={({ detail }) => updateLayout(index, detail)} />
|
||||||
|
|
@ -139,9 +137,9 @@
|
||||||
{#if optionalInput}
|
{#if optionalInput}
|
||||||
<OptionalInput {...exclude(optionalInput)} on:checked={({ detail }) => updateLayout(index, detail)} />
|
<OptionalInput {...exclude(optionalInput)} on:checked={({ detail }) => updateLayout(index, detail)} />
|
||||||
{/if}
|
{/if}
|
||||||
{@const pivotAssist = narrowWidgetProps(component.props, "PivotAssist")}
|
{@const pivotInput = narrowWidgetProps(component.props, "PivotInput")}
|
||||||
{#if pivotAssist}
|
{#if pivotInput}
|
||||||
<PivotAssist {...exclude(pivotAssist)} on:position={({ detail }) => updateLayout(index, detail)} />
|
<PivotInput {...exclude(pivotInput)} on:position={({ detail }) => updateLayout(index, detail)} />
|
||||||
{/if}
|
{/if}
|
||||||
{@const popoverButton = narrowWidgetProps(component.props, "PopoverButton")}
|
{@const popoverButton = narrowWidgetProps(component.props, "PopoverButton")}
|
||||||
{#if popoverButton}
|
{#if popoverButton}
|
||||||
|
|
@ -162,9 +160,9 @@
|
||||||
{#if separator}
|
{#if separator}
|
||||||
<Separator {...exclude(separator)} />
|
<Separator {...exclude(separator)} />
|
||||||
{/if}
|
{/if}
|
||||||
{@const swatchPairInput = narrowWidgetProps(component.props, "SwatchPairInput")}
|
{@const workingColorsButton = narrowWidgetProps(component.props, "WorkingColorsButton")}
|
||||||
{#if swatchPairInput}
|
{#if workingColorsButton}
|
||||||
<SwatchPairInput {...exclude(swatchPairInput)} />
|
<WorkingColorsButton {...exclude(workingColorsButton)} />
|
||||||
{/if}
|
{/if}
|
||||||
{@const textAreaInput = narrowWidgetProps(component.props, "TextAreaInput")}
|
{@const textAreaInput = narrowWidgetProps(component.props, "TextAreaInput")}
|
||||||
{#if textAreaInput}
|
{#if textAreaInput}
|
||||||
|
|
@ -190,13 +188,13 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss" global>
|
<style lang="scss" global>
|
||||||
.widget-column {
|
.widget-span.column {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.widget-row.widget-row {
|
.widget-span.row {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<LayoutRow class="breadcrumb-trail-buttons" {tooltip}>
|
<LayoutRow class="breadcrumb-trail-buttons" {tooltip}>
|
||||||
{#each labels as label, index (index)}
|
{#each labels as label, index}
|
||||||
<TextButton {label} emphasized={index === labels.length - 1} {disabled} action={() => !disabled && index !== labels.length - 1 && action(index)} />
|
<TextButton {label} emphasized={index === labels.length - 1} {disabled} action={() => !disabled && index !== labels.length - 1 && action(index)} />
|
||||||
{/each}
|
{/each}
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { MenuListEntry } from "@graphite/wasm-communication/messages";
|
||||||
|
|
||||||
|
import MenuList from "@graphite/components/floating-menus/MenuList.svelte";
|
||||||
|
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
||||||
|
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
||||||
|
|
||||||
|
export let entry: MenuListEntry;
|
||||||
|
|
||||||
|
let entryRef: MenuList;
|
||||||
|
|
||||||
|
$: (entry.ref = entryRef), entry.ref;
|
||||||
|
|
||||||
|
function clickEntry(e: MouseEvent) {
|
||||||
|
// If there's no menu to open, trigger the action but don't try to open its non-existant children
|
||||||
|
if ((entry.children?.length ?? 0) === 0) {
|
||||||
|
if (entry.action && !entry.disabled) entry.action();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Focus the target so that keyboard inputs are sent to the dropdown
|
||||||
|
(e.target as HTMLElement | undefined)?.focus();
|
||||||
|
|
||||||
|
if (entry.ref) {
|
||||||
|
entry.ref.open = true;
|
||||||
|
} else {
|
||||||
|
throw new Error("The menu bar floating menu has no associated ref");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="menu-list-button">
|
||||||
|
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||||
|
<div
|
||||||
|
on:click={(e) => clickEntry(e)}
|
||||||
|
on:keydown={(e) => entry.ref?.keydown(e, false)}
|
||||||
|
class="entry"
|
||||||
|
class:open={entry.ref?.open}
|
||||||
|
tabindex="0"
|
||||||
|
data-floating-menu-spawner={(entry.children?.length ?? 0) > 0 ? "" : "no-hover-transfer"}
|
||||||
|
>
|
||||||
|
{#if entry.icon}
|
||||||
|
<IconLabel icon={entry.icon} />
|
||||||
|
{/if}
|
||||||
|
{#if entry.label}
|
||||||
|
<TextLabel>{entry.label}</TextLabel>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{#if (entry.children?.length ?? 0) > 0}
|
||||||
|
<MenuList
|
||||||
|
on:open={({ detail }) => entry.ref && (entry.ref.open = detail)}
|
||||||
|
open={entry.ref?.open || false}
|
||||||
|
entries={entry.children || []}
|
||||||
|
direction="Bottom"
|
||||||
|
minWidth={240}
|
||||||
|
drawIcon={true}
|
||||||
|
bind:this={entryRef}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss" global>
|
||||||
|
.menu-list-button {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.entry {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
background: none;
|
||||||
|
padding: 0 8px;
|
||||||
|
margin: 0;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 2px;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&.open {
|
||||||
|
background: var(--color-5-dullgray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -88,12 +88,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.widget-row > & + .text-button,
|
.widget-span.row > & + .text-button,
|
||||||
.layout-row > & + .text-button {
|
.layout-row > & + .text-button {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.widget-column > & + .text-button,
|
.widget-span.column > & + .text-button,
|
||||||
.layout-column > & + .text-button {
|
.layout-column > & + .text-button {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<LayoutCol class="swatch-pair">
|
<LayoutCol class="working-colors-button">
|
||||||
<LayoutRow class="primary swatch">
|
<LayoutRow class="primary swatch">
|
||||||
<button on:click={clickPrimarySwatch} class:open={primaryOpen} style:--swatch-color={primary.toRgbaCSS()} data-floating-menu-spawner="no-hover-transfer" tabindex="0" />
|
<button on:click={clickPrimarySwatch} class:open={primaryOpen} style:--swatch-color={primary.toRgbaCSS()} data-floating-menu-spawner="no-hover-transfer" tabindex="0" />
|
||||||
<ColorPicker open={primaryOpen} on:open={({ detail }) => (primaryOpen = detail)} color={primary} on:color={({ detail }) => primaryColorChanged(detail)} direction="Right" />
|
<ColorPicker open={primaryOpen} on:open={({ detail }) => (primaryOpen = detail)} color={primary} on:color={({ detail }) => primaryColorChanged(detail)} direction="Right" />
|
||||||
|
|
@ -47,7 +47,7 @@
|
||||||
</LayoutCol>
|
</LayoutCol>
|
||||||
|
|
||||||
<style lang="scss" global>
|
<style lang="scss" global>
|
||||||
.swatch-pair {
|
.working-colors-button {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
|
||||||
.swatch {
|
.swatch {
|
||||||
|
|
@ -1,140 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { getContext, onMount } from "svelte";
|
|
||||||
|
|
||||||
import { platformIsMac } from "@graphite/utility-functions/platform";
|
|
||||||
import type { Editor } from "@graphite/wasm-communication/editor";
|
|
||||||
import { type KeyRaw, type LayoutKeysGroup, type MenuBarEntry, type MenuListEntry, UpdateMenuBarLayout } from "@graphite/wasm-communication/messages";
|
|
||||||
|
|
||||||
import MenuList from "@graphite/components/floating-menus/MenuList.svelte";
|
|
||||||
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
|
||||||
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
|
||||||
|
|
||||||
// TODO: Apparently, Safari does not support the Keyboard.lock() API but does relax its authority over certain keyboard shortcuts in fullscreen mode, which we should take advantage of
|
|
||||||
const accelKey = platformIsMac() ? "Command" : "Control";
|
|
||||||
const LOCK_REQUIRING_SHORTCUTS: KeyRaw[][] = [
|
|
||||||
[accelKey, "KeyW"],
|
|
||||||
[accelKey, "KeyN"],
|
|
||||||
[accelKey, "Shift", "KeyN"],
|
|
||||||
[accelKey, "KeyT"],
|
|
||||||
[accelKey, "Shift", "KeyT"],
|
|
||||||
];
|
|
||||||
|
|
||||||
const editor = getContext<Editor>("editor");
|
|
||||||
|
|
||||||
let entries: MenuListEntry[] = [];
|
|
||||||
|
|
||||||
function clickEntry(menuListEntry: MenuListEntry, e: MouseEvent) {
|
|
||||||
// If there's no menu to open, trigger the action but don't try to open its non-existant children
|
|
||||||
if (!menuListEntry.children || menuListEntry.children.length === 0) {
|
|
||||||
if (menuListEntry.action && !menuListEntry.disabled) menuListEntry.action();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Focus the target so that keyboard inputs are sent to the dropdown
|
|
||||||
(e.target as HTMLElement | undefined)?.focus();
|
|
||||||
|
|
||||||
if (menuListEntry.ref) {
|
|
||||||
menuListEntry.ref.open = true;
|
|
||||||
entries = entries;
|
|
||||||
} else {
|
|
||||||
throw new Error("The menu bar floating menu has no associated ref");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
editor.subscriptions.subscribeJsMessage(UpdateMenuBarLayout, (updateMenuBarLayout) => {
|
|
||||||
const arraysEqual = (a: KeyRaw[], b: KeyRaw[]): boolean => a.length === b.length && a.every((aValue, i) => aValue === b[i]);
|
|
||||||
const shortcutRequiresLock = (shortcut: LayoutKeysGroup): boolean => {
|
|
||||||
const shortcutKeys = shortcut.map((keyWithLabel) => keyWithLabel.key);
|
|
||||||
|
|
||||||
// If this shortcut matches any of the browser-reserved shortcuts
|
|
||||||
return LOCK_REQUIRING_SHORTCUTS.some((lockKeyCombo) => arraysEqual(shortcutKeys, lockKeyCombo));
|
|
||||||
};
|
|
||||||
|
|
||||||
const menuBarEntryToMenuListEntry = (entry: MenuBarEntry): MenuListEntry => ({
|
|
||||||
// From `MenuEntryCommon`
|
|
||||||
...entry,
|
|
||||||
|
|
||||||
// Shared names with fields that need to be converted from the type used in `MenuBarEntry` to that of `MenuListEntry`
|
|
||||||
action: () => editor.instance.updateLayout(updateMenuBarLayout.layoutTarget, entry.action.widgetId, undefined),
|
|
||||||
children: entry.children ? entry.children.map((entries) => entries.map((entry) => menuBarEntryToMenuListEntry(entry))) : undefined,
|
|
||||||
|
|
||||||
// New fields in `MenuListEntry`
|
|
||||||
shortcutRequiresLock: entry.shortcut ? shortcutRequiresLock(entry.shortcut.keys) : undefined,
|
|
||||||
value: undefined,
|
|
||||||
disabled: entry.disabled ?? undefined,
|
|
||||||
font: undefined,
|
|
||||||
ref: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
entries = updateMenuBarLayout.layout.map(menuBarEntryToMenuListEntry);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="menu-bar-input" data-menu-bar-input>
|
|
||||||
{#each entries as entry, index (index)}
|
|
||||||
<div class="entry-container">
|
|
||||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
|
||||||
<div
|
|
||||||
on:click={(e) => clickEntry(entry, e)}
|
|
||||||
on:keydown={(e) => entry.ref?.keydown(e, false)}
|
|
||||||
class="entry"
|
|
||||||
class:open={entry.ref?.open}
|
|
||||||
tabindex="0"
|
|
||||||
data-floating-menu-spawner={entry.children && entry.children.length > 0 ? "" : "no-hover-transfer"}
|
|
||||||
>
|
|
||||||
{#if entry.icon}
|
|
||||||
<IconLabel icon={entry.icon} />
|
|
||||||
{/if}
|
|
||||||
{#if entry.label}
|
|
||||||
<TextLabel>{entry.label}</TextLabel>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{#if entry.children && entry.children.length > 0}
|
|
||||||
<MenuList
|
|
||||||
on:open={({ detail }) => {
|
|
||||||
if (entry.ref) {
|
|
||||||
entry.ref.open = detail;
|
|
||||||
entries = entries;
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
open={entry.ref?.open || false}
|
|
||||||
entries={entry.children || []}
|
|
||||||
direction="Bottom"
|
|
||||||
minWidth={240}
|
|
||||||
drawIcon={true}
|
|
||||||
bind:this={entry.ref}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style lang="scss" global>
|
|
||||||
.menu-bar-input {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
.entry-container {
|
|
||||||
display: flex;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.entry {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
white-space: nowrap;
|
|
||||||
padding: 0 8px;
|
|
||||||
background: none;
|
|
||||||
border: 0;
|
|
||||||
margin: 0;
|
|
||||||
border-radius: 2px;
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&.open {
|
|
||||||
background: var(--color-5-dullgray);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="pivot-assist" class:disabled>
|
<div class="pivot-input" class:disabled>
|
||||||
<button on:click={() => setPosition("TopLeft")} class="row-1 col-1" class:active={position === "TopLeft"} tabindex="-1" {disabled}><div /></button>
|
<button on:click={() => setPosition("TopLeft")} class="row-1 col-1" class:active={position === "TopLeft"} tabindex="-1" {disabled}><div /></button>
|
||||||
<button on:click={() => setPosition("TopCenter")} class="row-1 col-2" class:active={position === "TopCenter"} tabindex="-1" {disabled}><div /></button>
|
<button on:click={() => setPosition("TopCenter")} class="row-1 col-2" class:active={position === "TopCenter"} tabindex="-1" {disabled}><div /></button>
|
||||||
<button on:click={() => setPosition("TopRight")} class="row-1 col-3" class:active={position === "TopRight"} tabindex="-1" {disabled}><div /></button>
|
<button on:click={() => setPosition("TopRight")} class="row-1 col-3" class:active={position === "TopRight"} tabindex="-1" {disabled}><div /></button>
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss" global>
|
<style lang="scss" global>
|
||||||
.pivot-assist {
|
.pivot-input {
|
||||||
position: relative;
|
position: relative;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<LayoutRow class="radio-input" classes={{ disabled }}>
|
<LayoutRow class="radio-input" classes={{ disabled }}>
|
||||||
{#each entries as entry, index (index)}
|
{#each entries as entry, index}
|
||||||
<button
|
<button
|
||||||
class:active={index === selectedIndex}
|
class:active={index === selectedIndex}
|
||||||
class:mixed={selectedIndex === undefined}
|
class:mixed={selectedIndex === undefined}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
export let mediumDivisions = 5;
|
export let mediumDivisions = 5;
|
||||||
export let minorDivisions = 2;
|
export let minorDivisions = 2;
|
||||||
|
|
||||||
let canvasRuler: HTMLDivElement | undefined;
|
let rulerInput: HTMLDivElement | undefined;
|
||||||
let rulerLength = 0;
|
let rulerLength = 0;
|
||||||
let svgBounds = { width: "0px", height: "0px" };
|
let svgBounds = { width: "0px", height: "0px" };
|
||||||
|
|
||||||
|
|
@ -76,11 +76,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resize() {
|
export function resize() {
|
||||||
if (!canvasRuler) return;
|
if (!rulerInput) return;
|
||||||
|
|
||||||
const isVertical = direction === "Vertical";
|
const isVertical = direction === "Vertical";
|
||||||
|
|
||||||
const newLength = isVertical ? canvasRuler.clientHeight : canvasRuler.clientWidth;
|
const newLength = isVertical ? rulerInput.clientHeight : rulerInput.clientWidth;
|
||||||
const roundedUp = (Math.floor(newLength / majorMarkSpacing) + 1) * majorMarkSpacing;
|
const roundedUp = (Math.floor(newLength / majorMarkSpacing) + 1) * majorMarkSpacing;
|
||||||
|
|
||||||
if (roundedUp !== rulerLength) {
|
if (roundedUp !== rulerLength) {
|
||||||
|
|
@ -98,17 +98,17 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class={`canvas-ruler ${direction.toLowerCase()}`} bind:this={canvasRuler}>
|
<div class={`ruler-input ${direction.toLowerCase()}`} bind:this={rulerInput}>
|
||||||
<svg style:width={svgBounds.width} style:height={svgBounds.height}>
|
<svg style:width={svgBounds.width} style:height={svgBounds.height}>
|
||||||
<path d={svgPath} />
|
<path d={svgPath} />
|
||||||
{#each svgTexts as svgText, index (index)}
|
{#each svgTexts as svgText}
|
||||||
<text transform={svgText.transform}>{svgText.text}</text>
|
<text transform={svgText.transform}>{svgText.text}</text>
|
||||||
{/each}
|
{/each}
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss" global>
|
<style lang="scss" global>
|
||||||
.canvas-ruler {
|
.ruler-input {
|
||||||
flex: 1 1 100%;
|
flex: 1 1 100%;
|
||||||
background: var(--color-4-dimgray);
|
background: var(--color-4-dimgray);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
@ -103,7 +103,7 @@
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class={`persistent-scrollbar ${direction.toLowerCase()}`}>
|
<div class={`scrollbar-input ${direction.toLowerCase()}`}>
|
||||||
<button class="arrow decrease" on:pointerdown={() => changePosition(-50)} tabindex="-1" />
|
<button class="arrow decrease" on:pointerdown={() => changePosition(-50)} tabindex="-1" />
|
||||||
<div class="scroll-track" bind:this={scrollTrack} on:pointerdown={grabArea}>
|
<div class="scroll-track" bind:this={scrollTrack} on:pointerdown={grabArea}>
|
||||||
<div class="scroll-thumb" on:pointerdown={grabHandle} class:dragging style:top={thumbTop} style:bottom={thumbBottom} style:left={thumbLeft} style:right={thumbRight} />
|
<div class="scroll-thumb" on:pointerdown={grabHandle} class:dragging style:top={thumbTop} style:bottom={thumbBottom} style:left={thumbLeft} style:right={thumbRight} />
|
||||||
|
|
@ -112,7 +112,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss" global>
|
<style lang="scss" global>
|
||||||
.persistent-scrollbar {
|
.scrollbar-input {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1 1 100%;
|
flex: 1 1 100%;
|
||||||
|
|
||||||
|
|
@ -120,11 +120,11 @@
|
||||||
<IconLabel class="user-input-label keyboard-lock-notice" icon="Info" tooltip={keyboardLockInfoMessage} />
|
<IconLabel class="user-input-label keyboard-lock-notice" icon="Info" tooltip={keyboardLockInfoMessage} />
|
||||||
{:else}
|
{:else}
|
||||||
<LayoutRow class="user-input-label" classes={{ "text-only": textOnly }}>
|
<LayoutRow class="user-input-label" classes={{ "text-only": textOnly }}>
|
||||||
{#each keysWithLabelsGroups as keysWithLabels, groupIndex (groupIndex)}
|
{#each keysWithLabelsGroups as keysWithLabels, groupIndex}
|
||||||
{#if groupIndex > 0}
|
{#if groupIndex > 0}
|
||||||
<Separator type="Related" />
|
<Separator type="Related" />
|
||||||
{/if}
|
{/if}
|
||||||
{#each keyTextOrIconList(keysWithLabels) as keyInfo, keyIndex (keyIndex)}
|
{#each keyTextOrIconList(keysWithLabels) as keyInfo}
|
||||||
<div class={`input-key ${keyInfo.width}`}>
|
<div class={`input-key ${keyInfo.width}`}>
|
||||||
{#if keyInfo.icon}
|
{#if keyInfo.icon}
|
||||||
<IconLabel icon={keyInfo.icon} />
|
<IconLabel icon={keyInfo.icon} />
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,15 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext } from "svelte";
|
import { getContext, onMount } from "svelte";
|
||||||
|
|
||||||
import type { PortfolioState } from "@graphite/state-providers/portfolio";
|
import type { PortfolioState } from "@graphite/state-providers/portfolio";
|
||||||
|
import { platformIsMac } from "@graphite/utility-functions/platform";
|
||||||
|
import type { Editor } from "@graphite/wasm-communication/editor";
|
||||||
|
import { type KeyRaw, type LayoutKeysGroup, type MenuBarEntry, type MenuListEntry, UpdateMenuBarLayout } from "@graphite/wasm-communication/messages";
|
||||||
|
|
||||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||||
import MenuBarInput from "@graphite/components/widgets/inputs/MenuBarInput.svelte";
|
import MenuListButton from "@graphite/components/widgets/buttons/MenuListButton.svelte";
|
||||||
import WindowButtonsMac from "@graphite/components/window/title-bar/WindowButtonsMac.svelte";
|
import WindowButtonsMac from "@graphite/components/window/title-bar/WindowButtonsMac.svelte";
|
||||||
import WindowButtonsWeb from "@graphite/components/window/title-bar/WindowButtonsWeb.svelte";
|
import WindowButtonsWeb from "@graphite/components/window/title-bar/WindowButtonsWeb.svelte";
|
||||||
import WindowButtonsWindows from "@graphite/components/window/title-bar/WindowButtonsWindows.svelte";
|
import WindowButtonsWindows from "@graphite/components/window/title-bar/WindowButtonsWindows.svelte";
|
||||||
|
|
@ -17,25 +20,73 @@
|
||||||
export let platform: Platform;
|
export let platform: Platform;
|
||||||
export let maximized: boolean;
|
export let maximized: boolean;
|
||||||
|
|
||||||
|
const editor = getContext<Editor>("editor");
|
||||||
const portfolio = getContext<PortfolioState>("portfolio");
|
const portfolio = getContext<PortfolioState>("portfolio");
|
||||||
|
|
||||||
|
// TODO: Apparently, Safari does not support the Keyboard.lock() API but does relax its authority over certain keyboard shortcuts in fullscreen mode, which we should take advantage of
|
||||||
|
const ACCEL_KEY = platformIsMac() ? "Command" : "Control";
|
||||||
|
const LOCK_REQUIRING_SHORTCUTS: KeyRaw[][] = [
|
||||||
|
[ACCEL_KEY, "KeyW"],
|
||||||
|
[ACCEL_KEY, "KeyN"],
|
||||||
|
[ACCEL_KEY, "Shift", "KeyN"],
|
||||||
|
[ACCEL_KEY, "KeyT"],
|
||||||
|
[ACCEL_KEY, "Shift", "KeyT"],
|
||||||
|
];
|
||||||
|
|
||||||
|
let entries: MenuListEntry[] = [];
|
||||||
|
|
||||||
$: docIndex = $portfolio.activeDocumentIndex;
|
$: docIndex = $portfolio.activeDocumentIndex;
|
||||||
$: displayName = $portfolio.documents[docIndex]?.displayName || "";
|
$: displayName = $portfolio.documents[docIndex]?.displayName || "";
|
||||||
$: windowTitle = `${displayName}${displayName && " - "}Graphite`;
|
$: windowTitle = `${displayName}${displayName && " - "}Graphite`;
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
const arraysEqual = (a: KeyRaw[], b: KeyRaw[]): boolean => a.length === b.length && a.every((aValue, i) => aValue === b[i]);
|
||||||
|
const shortcutRequiresLock = (shortcut: LayoutKeysGroup): boolean => {
|
||||||
|
const shortcutKeys = shortcut.map((keyWithLabel) => keyWithLabel.key);
|
||||||
|
|
||||||
|
// If this shortcut matches any of the browser-reserved shortcuts
|
||||||
|
return LOCK_REQUIRING_SHORTCUTS.some((lockKeyCombo) => arraysEqual(shortcutKeys, lockKeyCombo));
|
||||||
|
};
|
||||||
|
|
||||||
|
editor.subscriptions.subscribeJsMessage(UpdateMenuBarLayout, (updateMenuBarLayout) => {
|
||||||
|
const menuBarEntryToMenuListEntry = (entry: MenuBarEntry): MenuListEntry => ({
|
||||||
|
// From `MenuEntryCommon`
|
||||||
|
...entry,
|
||||||
|
|
||||||
|
// Shared names with fields that need to be converted from the type used in `MenuBarEntry` to that of `MenuListEntry`
|
||||||
|
action: () => editor.instance.updateLayout(updateMenuBarLayout.layoutTarget, entry.action.widgetId, undefined),
|
||||||
|
children: entry.children ? entry.children.map((entries) => entries.map((entry) => menuBarEntryToMenuListEntry(entry))) : undefined,
|
||||||
|
|
||||||
|
// New fields in `MenuListEntry`
|
||||||
|
shortcutRequiresLock: entry.shortcut ? shortcutRequiresLock(entry.shortcut.keys) : undefined,
|
||||||
|
value: undefined,
|
||||||
|
disabled: entry.disabled ?? undefined,
|
||||||
|
font: undefined,
|
||||||
|
ref: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
entries = updateMenuBarLayout.layout.map(menuBarEntryToMenuListEntry);
|
||||||
|
});
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<LayoutRow class="title-bar">
|
<LayoutRow class="title-bar">
|
||||||
<LayoutRow class="header-part">
|
<!-- Menu bar (or on Mac: window buttons) -->
|
||||||
|
<LayoutRow class="left">
|
||||||
{#if platform === "Mac"}
|
{#if platform === "Mac"}
|
||||||
<WindowButtonsMac {maximized} />
|
<WindowButtonsMac {maximized} />
|
||||||
{:else}
|
{:else}
|
||||||
<MenuBarInput />
|
{#each entries as entry}
|
||||||
|
<MenuListButton {entry} />
|
||||||
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
<LayoutRow class="header-part">
|
<!-- Document title -->
|
||||||
|
<LayoutRow class="center">
|
||||||
<WindowTitle text={windowTitle} />
|
<WindowTitle text={windowTitle} />
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
<LayoutRow class="header-part">
|
<!-- Window buttons (except on Mac) -->
|
||||||
|
<LayoutRow class="right">
|
||||||
{#if platform === "Windows" || platform === "Linux"}
|
{#if platform === "Windows" || platform === "Linux"}
|
||||||
<WindowButtonsWindows {maximized} />
|
<WindowButtonsWindows {maximized} />
|
||||||
{:else if platform === "Web"}
|
{:else if platform === "Web"}
|
||||||
|
|
@ -49,18 +100,18 @@
|
||||||
height: 28px;
|
height: 28px;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
|
||||||
.header-part {
|
> .layout-row {
|
||||||
flex: 1 1 100%;
|
flex: 1 1 100%;
|
||||||
|
|
||||||
&:nth-child(1) {
|
&.left {
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:nth-child(2) {
|
&.center {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:nth-child(3) {
|
&.right {
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@
|
||||||
<LayoutCol class="panel">
|
<LayoutCol class="panel">
|
||||||
<LayoutRow class="tab-bar" classes={{ "min-widths": tabMinWidths }}>
|
<LayoutRow class="tab-bar" classes={{ "min-widths": tabMinWidths }}>
|
||||||
<LayoutRow class="tab-group" scrollableX={true}>
|
<LayoutRow class="tab-group" scrollableX={true}>
|
||||||
{#each tabLabels as tabLabel, tabIndex (tabIndex)}
|
{#each tabLabels as tabLabel, tabIndex}
|
||||||
<LayoutRow
|
<LayoutRow
|
||||||
class="tab"
|
class="tab"
|
||||||
classes={{ active: tabIndex === tabActiveIndex }}
|
classes={{ active: tabIndex === tabActiveIndex }}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
import type { FrontendDocumentDetails } from "@graphite/wasm-communication/messages";
|
import type { FrontendDocumentDetails } from "@graphite/wasm-communication/messages";
|
||||||
|
|
||||||
import DialogModal from "@graphite/components/floating-menus/DialogModal.svelte";
|
import Dialog from "@graphite/components/floating-menus/Dialog.svelte";
|
||||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||||
import Panel from "@graphite/components/window/workspace/Panel.svelte";
|
import Panel from "@graphite/components/window/workspace/Panel.svelte";
|
||||||
|
|
@ -124,7 +124,7 @@
|
||||||
</LayoutCol>
|
</LayoutCol>
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
{#if $dialog.visible}
|
{#if $dialog.visible}
|
||||||
<DialogModal />
|
<Dialog />
|
||||||
{/if}
|
{/if}
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
||||||
// Keyboard events
|
// Keyboard events
|
||||||
|
|
||||||
async function shouldRedirectKeyboardEventToBackend(e: KeyboardEvent): Promise<boolean> {
|
async function shouldRedirectKeyboardEventToBackend(e: KeyboardEvent): Promise<boolean> {
|
||||||
// Don't redirect when a modal is covering the workspace
|
// Don't redirect when a dialog is covering the workspace
|
||||||
if (get(dialog).visible) return false;
|
if (get(dialog).visible) return false;
|
||||||
|
|
||||||
const key = await getLocalizedScanCode(e);
|
const key = await getLocalizedScanCode(e);
|
||||||
|
|
@ -157,7 +157,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
||||||
function onPointerDown(e: PointerEvent) {
|
function onPointerDown(e: PointerEvent) {
|
||||||
const { target } = e;
|
const { target } = e;
|
||||||
const isTargetingCanvas = target instanceof Element && target.closest("[data-viewport]");
|
const isTargetingCanvas = target instanceof Element && target.closest("[data-viewport]");
|
||||||
const inDialog = target instanceof Element && target.closest("[data-dialog-modal] [data-floating-menu-content]");
|
const inDialog = target instanceof Element && target.closest("[data-dialog] [data-floating-menu-content]");
|
||||||
const inTextInput = target === textToolInteractiveInputElement;
|
const inTextInput = target === textToolInteractiveInputElement;
|
||||||
|
|
||||||
if (get(dialog).visible && !inDialog) {
|
if (get(dialog).visible && !inDialog) {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import { Transform, Type, plainToClass } from "class-transformer";
|
||||||
import { type IconName, type IconSize } from "@graphite/utility-functions/icons";
|
import { type IconName, type IconSize } from "@graphite/utility-functions/icons";
|
||||||
import { type WasmEditorInstance, type WasmRawInstance } from "@graphite/wasm-communication/editor";
|
import { type WasmEditorInstance, type WasmRawInstance } from "@graphite/wasm-communication/editor";
|
||||||
|
|
||||||
|
import type MenuList from "@graphite/components/floating-menus/MenuList.svelte";
|
||||||
|
|
||||||
export class JsMessage {
|
export class JsMessage {
|
||||||
// The marker provides a way to check if an object is a sub-class constructor for a jsMessage.
|
// The marker provides a way to check if an object is a sub-class constructor for a jsMessage.
|
||||||
static readonly jsMessageMarker = true;
|
static readonly jsMessageMarker = true;
|
||||||
|
|
@ -791,7 +793,7 @@ export type MenuBarEntry = MenuEntryCommon & {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
// An entry in the all-encompassing MenuList component which defines all types of menus ranging from `MenuBarInput` to `DropdownInput` widgets
|
// An entry in the all-encompassing MenuList component which defines all types of menus (which are spawned by widgets like `MenuListButton` and `DropdownInput`)
|
||||||
export type MenuListEntry = MenuEntryCommon & {
|
export type MenuListEntry = MenuEntryCommon & {
|
||||||
action?: () => void;
|
action?: () => void;
|
||||||
children?: MenuListEntry[][];
|
children?: MenuListEntry[][];
|
||||||
|
|
@ -801,7 +803,7 @@ export type MenuListEntry = MenuEntryCommon & {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
font?: URL;
|
font?: URL;
|
||||||
ref?: any;
|
ref?: MenuList;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class CurveManipulatorGroup {
|
export class CurveManipulatorGroup {
|
||||||
|
|
@ -1010,7 +1012,7 @@ export class Separator extends WidgetProps {
|
||||||
type!: SeparatorType;
|
type!: SeparatorType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SwatchPairInput extends WidgetProps {
|
export class WorkingColorsButton extends WidgetProps {
|
||||||
@Type(() => Color)
|
@Type(() => Color)
|
||||||
primary!: Color;
|
primary!: Color;
|
||||||
|
|
||||||
|
|
@ -1119,7 +1121,7 @@ export class TextLabel extends WidgetProps {
|
||||||
|
|
||||||
export type PivotPosition = "None" | "TopLeft" | "TopCenter" | "TopRight" | "CenterLeft" | "Center" | "CenterRight" | "BottomLeft" | "BottomCenter" | "BottomRight";
|
export type PivotPosition = "None" | "TopLeft" | "TopCenter" | "TopRight" | "CenterLeft" | "Center" | "CenterRight" | "BottomLeft" | "BottomCenter" | "BottomRight";
|
||||||
|
|
||||||
export class PivotAssist extends WidgetProps {
|
export class PivotInput extends WidgetProps {
|
||||||
position!: PivotPosition;
|
position!: PivotPosition;
|
||||||
|
|
||||||
disabled!: boolean;
|
disabled!: boolean;
|
||||||
|
|
@ -1141,11 +1143,11 @@ const widgetSubTypes = [
|
||||||
{ value: NumberInput, name: "NumberInput" },
|
{ value: NumberInput, name: "NumberInput" },
|
||||||
{ value: OptionalInput, name: "OptionalInput" },
|
{ value: OptionalInput, name: "OptionalInput" },
|
||||||
{ value: ParameterExposeButton, name: "ParameterExposeButton" },
|
{ value: ParameterExposeButton, name: "ParameterExposeButton" },
|
||||||
{ value: PivotAssist, name: "PivotAssist" },
|
{ value: PivotInput, name: "PivotInput" },
|
||||||
{ value: PopoverButton, name: "PopoverButton" },
|
{ value: PopoverButton, name: "PopoverButton" },
|
||||||
{ value: RadioInput, name: "RadioInput" },
|
{ value: RadioInput, name: "RadioInput" },
|
||||||
{ value: Separator, name: "Separator" },
|
{ value: Separator, name: "Separator" },
|
||||||
{ value: SwatchPairInput, name: "SwatchPairInput" },
|
{ value: WorkingColorsButton, name: "WorkingColorsButton" },
|
||||||
{ value: TextAreaInput, name: "TextAreaInput" },
|
{ value: TextAreaInput, name: "TextAreaInput" },
|
||||||
{ value: TextButton, name: "TextButton" },
|
{ value: TextButton, name: "TextButton" },
|
||||||
{ value: TextInput, name: "TextInput" },
|
{ value: TextInput, name: "TextInput" },
|
||||||
|
|
@ -1259,16 +1261,16 @@ export function patchWidgetLayout(layout: /* &mut */ WidgetLayout, updates: Widg
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LayoutGroup = WidgetRow | WidgetColumn | WidgetSection;
|
export type LayoutGroup = WidgetSpanRow | WidgetSpanColumn | WidgetSection;
|
||||||
|
|
||||||
export type WidgetColumn = { columnWidgets: Widget[] };
|
export type WidgetSpanColumn = { columnWidgets: Widget[] };
|
||||||
export function isWidgetColumn(layoutColumn: LayoutGroup): layoutColumn is WidgetColumn {
|
export function isWidgetSpanColumn(layoutColumn: LayoutGroup): layoutColumn is WidgetSpanColumn {
|
||||||
return Boolean((layoutColumn as WidgetColumn).columnWidgets);
|
return Boolean((layoutColumn as WidgetSpanColumn).columnWidgets);
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WidgetRow = { rowWidgets: Widget[] };
|
export type WidgetSpanRow = { rowWidgets: Widget[] };
|
||||||
export function isWidgetRow(layoutRow: LayoutGroup): layoutRow is WidgetRow {
|
export function isWidgetSpanRow(layoutRow: LayoutGroup): layoutRow is WidgetSpanRow {
|
||||||
return Boolean((layoutRow as WidgetRow).rowWidgets);
|
return Boolean((layoutRow as WidgetSpanRow).rowWidgets);
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WidgetSection = { name: string; layout: LayoutGroup[] };
|
export type WidgetSection = { name: string; layout: LayoutGroup[] };
|
||||||
|
|
@ -1301,12 +1303,12 @@ function createLayoutGroup(layoutGroup: any): LayoutGroup {
|
||||||
if (layoutGroup.column) {
|
if (layoutGroup.column) {
|
||||||
const columnWidgets = hoistWidgetHolders(layoutGroup.column.columnWidgets);
|
const columnWidgets = hoistWidgetHolders(layoutGroup.column.columnWidgets);
|
||||||
|
|
||||||
const result: WidgetColumn = { columnWidgets };
|
const result: WidgetSpanColumn = { columnWidgets };
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (layoutGroup.row) {
|
if (layoutGroup.row) {
|
||||||
const result: WidgetRow = { rowWidgets: hoistWidgetHolders(layoutGroup.row.rowWidgets) };
|
const result: WidgetSpanRow = { rowWidgets: hoistWidgetHolders(layoutGroup.row.rowWidgets) };
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ order = 1 # Page number after chapter intro
|
||||||
- Interface
|
- Interface
|
||||||
- Title bar
|
- Title bar
|
||||||
- Menu bar
|
- Menu bar
|
||||||
- Focused document title
|
- Document title
|
||||||
- Window buttons
|
- Window buttons
|
||||||
- Workspace
|
- Workspace
|
||||||
- Panel interface (tab, pin, options bar, left menu)
|
- Panel interface (tab, pin, options bar, left menu)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue