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.
|
||||
//!
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use crate::messages::layout::utility_types::widget_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 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);
|
||||
responses.add(callback_message);
|
||||
}
|
||||
Widget::PivotAssist(pivot_assist) => {
|
||||
let update_value = value.as_str().expect("RadioInput update was not of type: u64");
|
||||
pivot_assist.position = update_value.into();
|
||||
let callback_message = (pivot_assist.on_update.callback)(pivot_assist);
|
||||
Widget::PivotInput(pivot_input) => {
|
||||
let update_value = value.as_str().expect("PivotInput update was not of type: u64");
|
||||
pivot_input.position = update_value.into();
|
||||
let callback_message = (pivot_input.on_update.callback)(pivot_input);
|
||||
responses.add(callback_message);
|
||||
}
|
||||
Widget::PopoverButton(_) => {}
|
||||
|
|
@ -210,7 +210,6 @@ impl<F: Fn(&MessageDiscriminant) -> Vec<KeysGroup>> MessageHandler<LayoutMessage
|
|||
responses.add(callback_message);
|
||||
}
|
||||
Widget::Separator(_) => {}
|
||||
Widget::SwatchPairInput(_) => {}
|
||||
Widget::TextAreaInput(text_area_input) => {
|
||||
let update_value = value.as_str().expect("TextAreaInput update was not of type: string");
|
||||
text_area_input.value = update_value.into();
|
||||
|
|
@ -228,6 +227,7 @@ impl<F: Fn(&MessageDiscriminant) -> Vec<KeysGroup>> MessageHandler<LayoutMessage
|
|||
responses.add(callback_message);
|
||||
}
|
||||
Widget::TextLabel(_) => {}
|
||||
Widget::WorkingColorsButton(_) => {}
|
||||
};
|
||||
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::input_widgets::*;
|
||||
use super::widgets::label_widgets::*;
|
||||
|
|
@ -337,7 +336,7 @@ impl LayoutGroup {
|
|||
Widget::TextInput(x) => &mut x.tooltip,
|
||||
Widget::TextLabel(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() {
|
||||
*val = tooltip.clone();
|
||||
|
|
@ -483,15 +482,15 @@ pub enum Widget {
|
|||
NumberInput(NumberInput),
|
||||
OptionalInput(OptionalInput),
|
||||
ParameterExposeButton(ParameterExposeButton),
|
||||
PivotAssist(PivotAssist),
|
||||
PivotInput(PivotInput),
|
||||
PopoverButton(PopoverButton),
|
||||
RadioInput(RadioInput),
|
||||
Separator(Separator),
|
||||
SwatchPairInput(SwatchPairInput),
|
||||
TextAreaInput(TextAreaInput),
|
||||
TextButton(TextButton),
|
||||
TextInput(TextInput),
|
||||
TextLabel(TextLabel),
|
||||
WorkingColorsButton(WorkingColorsButton),
|
||||
}
|
||||
|
||||
/// 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::CurveInput(_)
|
||||
| Widget::InvisibleStandinInput(_)
|
||||
| Widget::PivotAssist(_)
|
||||
| Widget::PivotInput(_)
|
||||
| Widget::RadioInput(_)
|
||||
| Widget::Separator(_)
|
||||
| Widget::SwatchPairInput(_)
|
||||
| Widget::TextAreaInput(_)
|
||||
| Widget::TextInput(_)
|
||||
| Widget::TextLabel(_) => None,
|
||||
| Widget::TextLabel(_)
|
||||
| Widget::WorkingColorsButton(_) => None,
|
||||
};
|
||||
if let Some((tooltip, Some(tooltip_shortcut))) = &mut tooltip_shortcut {
|
||||
apply_shortcut_to_tooltip(tooltip_shortcut, tooltip);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ pub mod widgets;
|
|||
|
||||
pub mod widget_prelude {
|
||||
pub use super::layout_widget::*;
|
||||
pub use super::widgets::assist_widgets::*;
|
||||
pub use super::widgets::button_widgets::*;
|
||||
pub use super::widgets::input_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>,
|
||||
}
|
||||
|
||||
#[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)]
|
||||
#[derivative(Debug, PartialEq, Default)]
|
||||
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::LayerId;
|
||||
use graphene_core::raster::color::Color;
|
||||
use graphene_core::raster::curve::Curve;
|
||||
use graphite_proc_macros::WidgetBuilder;
|
||||
|
||||
use derivative::*;
|
||||
use glam::DVec2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Derivative, Serialize, Deserialize, WidgetBuilder, specta::Type)]
|
||||
|
|
@ -339,16 +339,6 @@ pub struct RadioEntryData {
|
|||
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)]
|
||||
#[derivative(Debug, PartialEq, Default)]
|
||||
pub struct TextAreaInput {
|
||||
|
|
@ -405,3 +395,99 @@ pub struct CurveInput {
|
|||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
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 input_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(
|
||||
PivotAssist::new(pivot.into())
|
||||
.on_update(update_value(|pivot: &PivotAssist| TaggedValue::DVec2(Into::<Option<DVec2>>::into(pivot.position).unwrap()), node_id, 5))
|
||||
PivotInput::new(pivot.into())
|
||||
.on_update(update_value(|pivot: &PivotInput| TaggedValue::DVec2(Into::<Option<DVec2>>::into(pivot.position).unwrap()), node_id, 5))
|
||||
.widget_holder(),
|
||||
);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -167,8 +167,8 @@ fn node_section_transform(layer: &Layer, persistent_data: &PersistentData) -> La
|
|||
widgets: vec![
|
||||
TextLabel::new("Location").widget_holder(),
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
PivotAssist::new(layer.pivot.into())
|
||||
.on_update(|pivot_assist: &PivotAssist| PropertiesPanelMessage::SetPivot { new_position: pivot_assist.position }.into())
|
||||
PivotInput::new(layer.pivot.into())
|
||||
.on_update(|pivot_input: &PivotInput| PropertiesPanelMessage::SetPivot { new_position: pivot_input.position }.into())
|
||||
.widget_holder(),
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
NumberInput::new(Some(layer.transform.x() + pivot.x))
|
||||
|
|
|
|||
|
|
@ -116,8 +116,8 @@ impl SelectTool {
|
|||
// }
|
||||
|
||||
fn pivot_widget(&self, disabled: bool) -> WidgetHolder {
|
||||
PivotAssist::new(self.tool_data.pivot.to_pivot_position())
|
||||
.on_update(|pivot_assist: &PivotAssist| SelectToolMessage::SetPivot { position: pivot_assist.position }.into())
|
||||
PivotInput::new(self.tool_data.pivot.to_pivot_position())
|
||||
.on_update(|pivot_input: &PivotInput| SelectToolMessage::SetPivot { position: pivot_input.position }.into())
|
||||
.disabled(disabled)
|
||||
.widget_holder()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ impl DocumentToolData {
|
|||
pub fn update_working_colors(&self, responses: &mut VecDeque<Message>) {
|
||||
let layout = WidgetLayout::new(vec![
|
||||
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 {
|
||||
widgets: vec![
|
||||
|
|
|
|||
|
|
@ -272,10 +272,10 @@
|
|||
.popover-button,
|
||||
.color-button > button,
|
||||
.color-picker .preset-color,
|
||||
.swatch-pair .swatch > button,
|
||||
.working-colors-button .swatch > button,
|
||||
.radio-input button,
|
||||
.menu-list,
|
||||
.menu-bar-input .entry,
|
||||
.menu-list-button .entry,
|
||||
.layer-tree .expand-arrow,
|
||||
.widget-section .header {
|
||||
&:focus-visible {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Each component represents a (usually reusable) part of the Graphite editor GUI.
|
|||
|
||||
## 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/`
|
||||
|
||||
|
|
|
|||
|
|
@ -319,7 +319,7 @@
|
|||
<TextLabel tooltip="Red/Green/Blue channels of the color, integers 0–255">RGB</TextLabel>
|
||||
<Separator />
|
||||
<LayoutRow>
|
||||
{#each rgbChannels as [channel, strength], index (channel)}
|
||||
{#each rgbChannels as [channel, strength], index}
|
||||
{#if index > 0}
|
||||
<Separator type="Related" />
|
||||
{/if}
|
||||
|
|
@ -343,7 +343,7 @@
|
|||
>
|
||||
<Separator />
|
||||
<LayoutRow>
|
||||
{#each hsvChannels as [channel, strength], index (channel)}
|
||||
{#each hsvChannels as [channel, strength], index}
|
||||
{#if index > 0}
|
||||
<Separator type="Related" />
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
</script>
|
||||
|
||||
<!-- 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">
|
||||
<!-- `$dialog.icon` class exists to provide special sizing in CSS to specific icons -->
|
||||
<IconLabel icon={$dialog.icon} class={$dialog.icon.toLowerCase()} />
|
||||
|
|
@ -39,11 +39,11 @@
|
|||
{/if}
|
||||
{#if $dialog.panicDetails}
|
||||
<div class="widget-layout details">
|
||||
<div class="widget-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-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-row">
|
||||
<div class="widget-span row"><TextLabel bold={true}>The editor crashed — sorry about that</TextLabel></div>
|
||||
<div class="widget-span row"><TextLabel>Please report this by filing an issue on GitHub:</TextLabel></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-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-span row">
|
||||
<TextButton
|
||||
label="Clear Saved Documents"
|
||||
icon="Trash"
|
||||
|
|
@ -75,7 +75,7 @@
|
|||
</FloatingMenu>
|
||||
|
||||
<style lang="scss" global>
|
||||
.dialog-modal {
|
||||
.dialog {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
width: 100%;
|
||||
|
|
@ -103,7 +103,7 @@
|
|||
|
||||
const menuOpen = open;
|
||||
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) => {
|
||||
if (highlightedEntry.ref && highlightedEntry.children?.length) {
|
||||
|
|
|
|||
|
|
@ -27,12 +27,12 @@
|
|||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
import Graph from "@graphite/components/views/Graph.svelte";
|
||||
import CanvasRuler from "@graphite/components/widgets/metrics/CanvasRuler.svelte";
|
||||
import PersistentScrollbar from "@graphite/components/widgets/metrics/PersistentScrollbar.svelte";
|
||||
import RulerInput from "@graphite/components/widgets/inputs/RulerInput.svelte";
|
||||
import ScrollbarInput from "@graphite/components/widgets/inputs/ScrollbarInput.svelte";
|
||||
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
||||
|
||||
let rulerHorizontal: CanvasRuler | undefined;
|
||||
let rulerVertical: CanvasRuler | undefined;
|
||||
let rulerHorizontal: RulerInput | undefined;
|
||||
let rulerVertical: RulerInput | undefined;
|
||||
let viewport: HTMLDivElement | undefined;
|
||||
|
||||
const editor = getContext<Editor>("editor");
|
||||
|
|
@ -441,11 +441,11 @@
|
|||
</LayoutCol>
|
||||
<LayoutCol class="table">
|
||||
<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 class="viewport-container">
|
||||
<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 class="viewport-container" styles={{ cursor: canvasCursor }}>
|
||||
{#if cursorEyedropper}
|
||||
|
|
@ -482,7 +482,7 @@
|
|||
</div>
|
||||
</LayoutCol>
|
||||
<LayoutCol class="ruler-or-scrollbar right-scrollbar">
|
||||
<PersistentScrollbar
|
||||
<ScrollbarInput
|
||||
direction="Vertical"
|
||||
handleLength={scrollbarSize.y}
|
||||
handlePosition={scrollbarPos.y}
|
||||
|
|
@ -492,7 +492,7 @@
|
|||
</LayoutCol>
|
||||
</LayoutRow>
|
||||
<LayoutRow class="ruler-or-scrollbar bottom-scrollbar">
|
||||
<PersistentScrollbar
|
||||
<ScrollbarInput
|
||||
direction="Horizontal"
|
||||
handleLength={scrollbarSize.x}
|
||||
handlePosition={scrollbarPos.x}
|
||||
|
|
@ -572,10 +572,10 @@
|
|||
.widget-layout:last-of-type {
|
||||
height: auto;
|
||||
|
||||
.widget-row {
|
||||
.widget-span.row {
|
||||
min-height: 0;
|
||||
|
||||
.swatch-pair {
|
||||
.working-colors-button {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
|
@ -594,16 +594,16 @@
|
|||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.top-ruler .canvas-ruler {
|
||||
.top-ruler .ruler-input {
|
||||
padding-left: 16px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.right-scrollbar .persistent-scrollbar {
|
||||
.right-scrollbar .scrollbar-input {
|
||||
margin-top: -16px;
|
||||
}
|
||||
|
||||
.bottom-scrollbar .persistent-scrollbar {
|
||||
.bottom-scrollbar .scrollbar-input {
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<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 WidgetRow from "@graphite/components/widgets/WidgetRow.svelte";
|
||||
import WidgetSection from "@graphite/components/widgets/WidgetSection.svelte";
|
||||
import WidgetSpan from "@graphite/components/widgets/WidgetSpan.svelte";
|
||||
|
||||
export let layout: WidgetLayout;
|
||||
let className = "";
|
||||
|
|
@ -14,15 +14,14 @@
|
|||
.join(" ");
|
||||
</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()}>
|
||||
{#each layout.layout as layoutGroup, index (index)}
|
||||
{#if isWidgetColumn(layoutGroup) || isWidgetRow(layoutGroup)}
|
||||
<WidgetRow widgetData={layoutGroup} layoutTarget={layout.layoutTarget} />
|
||||
{#each layout.layout as layoutGroup}
|
||||
{#if isWidgetSpanRow(layoutGroup) || isWidgetSpanColumn(layoutGroup)}
|
||||
<WidgetSpan widgetData={layoutGroup} layoutTarget={layout.layoutTarget} />
|
||||
{:else if isWidgetSection(layoutGroup)}
|
||||
<WidgetSection widgetData={layoutGroup} layoutTarget={layout.layoutTarget} />
|
||||
{: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}
|
||||
{/each}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<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 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;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
|
@ -20,9 +20,11 @@
|
|||
</button>
|
||||
{#if expanded}
|
||||
<LayoutCol class="body">
|
||||
{#each widgetData.layout as layoutGroup, index (index)}
|
||||
{#if isWidgetRow(layoutGroup)}
|
||||
<WidgetRow widgetData={layoutGroup} {layoutTarget} />
|
||||
{#each widgetData.layout as layoutGroup}
|
||||
{#if isWidgetSpanRow(layoutGroup)}
|
||||
<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)}
|
||||
<svelte:self widgetData={layoutGroup} {layoutTarget} />
|
||||
{:else}
|
||||
|
|
@ -105,7 +107,7 @@
|
|||
border-radius: 0 0 4px 4px;
|
||||
overflow: hidden;
|
||||
|
||||
.widget-row {
|
||||
.widget-span.row {
|
||||
&:first-child {
|
||||
margin-top: calc(4px - 1px);
|
||||
}
|
||||
|
|
@ -1,19 +1,18 @@
|
|||
<script lang="ts">
|
||||
import { getContext } from "svelte";
|
||||
|
||||
import ColorButton from "./buttons/ColorButton.svelte";
|
||||
|
||||
import { debouncer } from "@graphite/utility-functions/debounce";
|
||||
import type { Editor } from "@graphite/wasm-communication/editor";
|
||||
import type { Widget, WidgetColumn, WidgetRow } from "@graphite/wasm-communication/messages";
|
||||
import { narrowWidgetProps, isWidgetColumn, isWidgetRow } from "@graphite/wasm-communication/messages";
|
||||
import type { Widget, WidgetSpanColumn, WidgetSpanRow } 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 ColorButton from "@graphite/components/widgets/buttons/ColorButton.svelte";
|
||||
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
|
||||
import ParameterExposeButton from "@graphite/components/widgets/buttons/ParameterExposeButton.svelte";
|
||||
import PopoverButton from "@graphite/components/widgets/buttons/PopoverButton.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 CurveInput from "@graphite/components/widgets/inputs/CurveInput.svelte";
|
||||
import DropdownInput from "@graphite/components/widgets/inputs/DropdownInput.svelte";
|
||||
|
|
@ -21,8 +20,8 @@
|
|||
import LayerReferenceInput from "@graphite/components/widgets/inputs/LayerReferenceInput.svelte";
|
||||
import NumberInput from "@graphite/components/widgets/inputs/NumberInput.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 SwatchPairInput from "@graphite/components/widgets/inputs/SwatchPairInput.svelte";
|
||||
import TextAreaInput from "@graphite/components/widgets/inputs/TextAreaInput.svelte";
|
||||
import TextInput from "@graphite/components/widgets/inputs/TextInput.svelte";
|
||||
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
||||
|
|
@ -35,7 +34,7 @@
|
|||
|
||||
const editor = getContext<Editor>("editor");
|
||||
|
||||
export let widgetData: WidgetColumn | WidgetRow;
|
||||
export let widgetData: WidgetSpanRow | WidgetSpanColumn;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export let layoutTarget: any;
|
||||
|
||||
|
|
@ -43,16 +42,15 @@
|
|||
$: widgets = watchWidgets(widgetData);
|
||||
$: widgetsAndNextSiblingIsSuffix = watchWidgetsAndNextSiblingIsSuffix(widgets);
|
||||
|
||||
function watchDirection(widgetData: WidgetRow | WidgetColumn): "row" | "column" | "ERROR" {
|
||||
if (isWidgetRow(widgetData)) return "row";
|
||||
if (isWidgetColumn(widgetData)) return "column";
|
||||
return "ERROR";
|
||||
function watchDirection(widgetData: WidgetSpanRow | WidgetSpanColumn): "row" | "column" | undefined {
|
||||
if (isWidgetSpanRow(widgetData)) return "row";
|
||||
if (isWidgetSpanColumn(widgetData)) return "column";
|
||||
}
|
||||
|
||||
function watchWidgets(widgetData: WidgetRow | WidgetColumn): Widget[] {
|
||||
function watchWidgets(widgetData: WidgetSpanRow | WidgetSpanColumn): Widget[] {
|
||||
let widgets: Widget[] = [];
|
||||
if (isWidgetRow(widgetData)) widgets = widgetData.rowWidgets;
|
||||
else if (isWidgetColumn(widgetData)) widgets = widgetData.columnWidgets;
|
||||
if (isWidgetSpanRow(widgetData)) widgets = widgetData.rowWidgets;
|
||||
else if (isWidgetSpanColumn(widgetData)) widgets = widgetData.columnWidgets;
|
||||
return widgets;
|
||||
}
|
||||
|
||||
|
|
@ -83,8 +81,8 @@
|
|||
<!-- 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 -->
|
||||
|
||||
<div class={`widget-${direction}`}>
|
||||
{#each widgetsAndNextSiblingIsSuffix as [component, nextIsSuffix], index (index)}
|
||||
<div class="widget-span" class:row={direction === "row"} class:column={direction === "column"}>
|
||||
{#each widgetsAndNextSiblingIsSuffix as [component, nextIsSuffix], index}
|
||||
{@const checkboxInput = narrowWidgetProps(component.props, "CheckboxInput")}
|
||||
{#if checkboxInput}
|
||||
<CheckboxInput {...exclude(checkboxInput)} on:checked={({ detail }) => updateLayout(index, detail)} />
|
||||
|
|
@ -139,9 +137,9 @@
|
|||
{#if optionalInput}
|
||||
<OptionalInput {...exclude(optionalInput)} on:checked={({ detail }) => updateLayout(index, detail)} />
|
||||
{/if}
|
||||
{@const pivotAssist = narrowWidgetProps(component.props, "PivotAssist")}
|
||||
{#if pivotAssist}
|
||||
<PivotAssist {...exclude(pivotAssist)} on:position={({ detail }) => updateLayout(index, detail)} />
|
||||
{@const pivotInput = narrowWidgetProps(component.props, "PivotInput")}
|
||||
{#if pivotInput}
|
||||
<PivotInput {...exclude(pivotInput)} on:position={({ detail }) => updateLayout(index, detail)} />
|
||||
{/if}
|
||||
{@const popoverButton = narrowWidgetProps(component.props, "PopoverButton")}
|
||||
{#if popoverButton}
|
||||
|
|
@ -162,9 +160,9 @@
|
|||
{#if separator}
|
||||
<Separator {...exclude(separator)} />
|
||||
{/if}
|
||||
{@const swatchPairInput = narrowWidgetProps(component.props, "SwatchPairInput")}
|
||||
{#if swatchPairInput}
|
||||
<SwatchPairInput {...exclude(swatchPairInput)} />
|
||||
{@const workingColorsButton = narrowWidgetProps(component.props, "WorkingColorsButton")}
|
||||
{#if workingColorsButton}
|
||||
<WorkingColorsButton {...exclude(workingColorsButton)} />
|
||||
{/if}
|
||||
{@const textAreaInput = narrowWidgetProps(component.props, "TextAreaInput")}
|
||||
{#if textAreaInput}
|
||||
|
|
@ -190,13 +188,13 @@
|
|||
</div>
|
||||
|
||||
<style lang="scss" global>
|
||||
.widget-column {
|
||||
.widget-span.column {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.widget-row.widget-row {
|
||||
.widget-span.row {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
min-height: 32px;
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
</script>
|
||||
|
||||
<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)} />
|
||||
{/each}
|
||||
</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 {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.widget-column > & + .text-button,
|
||||
.widget-span.column > & + .text-button,
|
||||
.layout-column > & + .text-button {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<LayoutCol class="swatch-pair">
|
||||
<LayoutCol class="working-colors-button">
|
||||
<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" />
|
||||
<ColorPicker open={primaryOpen} on:open={({ detail }) => (primaryOpen = detail)} color={primary} on:color={({ detail }) => primaryColorChanged(detail)} direction="Right" />
|
||||
|
|
@ -47,7 +47,7 @@
|
|||
</LayoutCol>
|
||||
|
||||
<style lang="scss" global>
|
||||
.swatch-pair {
|
||||
.working-colors-button {
|
||||
flex: 0 0 auto;
|
||||
|
||||
.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>
|
||||
|
||||
<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("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>
|
||||
|
|
@ -27,7 +27,7 @@
|
|||
</div>
|
||||
|
||||
<style lang="scss" global>
|
||||
.pivot-assist {
|
||||
.pivot-input {
|
||||
position: relative;
|
||||
flex: 0 0 auto;
|
||||
width: 24px;
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
</script>
|
||||
|
||||
<LayoutRow class="radio-input" classes={{ disabled }}>
|
||||
{#each entries as entry, index (index)}
|
||||
{#each entries as entry, index}
|
||||
<button
|
||||
class:active={index === selectedIndex}
|
||||
class:mixed={selectedIndex === undefined}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
export let mediumDivisions = 5;
|
||||
export let minorDivisions = 2;
|
||||
|
||||
let canvasRuler: HTMLDivElement | undefined;
|
||||
let rulerInput: HTMLDivElement | undefined;
|
||||
let rulerLength = 0;
|
||||
let svgBounds = { width: "0px", height: "0px" };
|
||||
|
||||
|
|
@ -76,11 +76,11 @@
|
|||
}
|
||||
|
||||
export function resize() {
|
||||
if (!canvasRuler) return;
|
||||
if (!rulerInput) return;
|
||||
|
||||
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;
|
||||
|
||||
if (roundedUp !== rulerLength) {
|
||||
|
|
@ -98,17 +98,17 @@
|
|||
}
|
||||
</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}>
|
||||
<path d={svgPath} />
|
||||
{#each svgTexts as svgText, index (index)}
|
||||
{#each svgTexts as svgText}
|
||||
<text transform={svgText.transform}>{svgText.text}</text>
|
||||
{/each}
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<style lang="scss" global>
|
||||
.canvas-ruler {
|
||||
.ruler-input {
|
||||
flex: 1 1 100%;
|
||||
background: var(--color-4-dimgray);
|
||||
overflow: hidden;
|
||||
|
|
@ -103,7 +103,7 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<div class={`persistent-scrollbar ${direction.toLowerCase()}`}>
|
||||
<div class={`scrollbar-input ${direction.toLowerCase()}`}>
|
||||
<button class="arrow decrease" on:pointerdown={() => changePosition(-50)} tabindex="-1" />
|
||||
<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} />
|
||||
|
|
@ -112,7 +112,7 @@
|
|||
</div>
|
||||
|
||||
<style lang="scss" global>
|
||||
.persistent-scrollbar {
|
||||
.scrollbar-input {
|
||||
display: flex;
|
||||
flex: 1 1 100%;
|
||||
|
||||
|
|
@ -120,11 +120,11 @@
|
|||
<IconLabel class="user-input-label keyboard-lock-notice" icon="Info" tooltip={keyboardLockInfoMessage} />
|
||||
{:else}
|
||||
<LayoutRow class="user-input-label" classes={{ "text-only": textOnly }}>
|
||||
{#each keysWithLabelsGroups as keysWithLabels, groupIndex (groupIndex)}
|
||||
{#each keysWithLabelsGroups as keysWithLabels, groupIndex}
|
||||
{#if groupIndex > 0}
|
||||
<Separator type="Related" />
|
||||
{/if}
|
||||
{#each keyTextOrIconList(keysWithLabels) as keyInfo, keyIndex (keyIndex)}
|
||||
{#each keyTextOrIconList(keysWithLabels) as keyInfo}
|
||||
<div class={`input-key ${keyInfo.width}`}>
|
||||
{#if keyInfo.icon}
|
||||
<IconLabel icon={keyInfo.icon} />
|
||||
|
|
|
|||
|
|
@ -3,12 +3,15 @@
|
|||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { getContext } from "svelte";
|
||||
import { getContext, onMount } from "svelte";
|
||||
|
||||
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 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 WindowButtonsWeb from "@graphite/components/window/title-bar/WindowButtonsWeb.svelte";
|
||||
import WindowButtonsWindows from "@graphite/components/window/title-bar/WindowButtonsWindows.svelte";
|
||||
|
|
@ -17,25 +20,73 @@
|
|||
export let platform: Platform;
|
||||
export let maximized: boolean;
|
||||
|
||||
const editor = getContext<Editor>("editor");
|
||||
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;
|
||||
$: displayName = $portfolio.documents[docIndex]?.displayName || "";
|
||||
$: 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>
|
||||
|
||||
<LayoutRow class="title-bar">
|
||||
<LayoutRow class="header-part">
|
||||
<!-- Menu bar (or on Mac: window buttons) -->
|
||||
<LayoutRow class="left">
|
||||
{#if platform === "Mac"}
|
||||
<WindowButtonsMac {maximized} />
|
||||
{:else}
|
||||
<MenuBarInput />
|
||||
{#each entries as entry}
|
||||
<MenuListButton {entry} />
|
||||
{/each}
|
||||
{/if}
|
||||
</LayoutRow>
|
||||
<LayoutRow class="header-part">
|
||||
<!-- Document title -->
|
||||
<LayoutRow class="center">
|
||||
<WindowTitle text={windowTitle} />
|
||||
</LayoutRow>
|
||||
<LayoutRow class="header-part">
|
||||
<!-- Window buttons (except on Mac) -->
|
||||
<LayoutRow class="right">
|
||||
{#if platform === "Windows" || platform === "Linux"}
|
||||
<WindowButtonsWindows {maximized} />
|
||||
{:else if platform === "Web"}
|
||||
|
|
@ -49,18 +100,18 @@
|
|||
height: 28px;
|
||||
flex: 0 0 auto;
|
||||
|
||||
.header-part {
|
||||
> .layout-row {
|
||||
flex: 1 1 100%;
|
||||
|
||||
&:nth-child(1) {
|
||||
&.left {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
&.center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
&.right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@
|
|||
<LayoutCol class="panel">
|
||||
<LayoutRow class="tab-bar" classes={{ "min-widths": tabMinWidths }}>
|
||||
<LayoutRow class="tab-group" scrollableX={true}>
|
||||
{#each tabLabels as tabLabel, tabIndex (tabIndex)}
|
||||
{#each tabLabels as tabLabel, tabIndex}
|
||||
<LayoutRow
|
||||
class="tab"
|
||||
classes={{ active: tabIndex === tabActiveIndex }}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
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 LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
import Panel from "@graphite/components/window/workspace/Panel.svelte";
|
||||
|
|
@ -124,7 +124,7 @@
|
|||
</LayoutCol>
|
||||
</LayoutRow>
|
||||
{#if $dialog.visible}
|
||||
<DialogModal />
|
||||
<Dialog />
|
||||
{/if}
|
||||
</LayoutRow>
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
|||
// Keyboard events
|
||||
|
||||
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;
|
||||
|
||||
const key = await getLocalizedScanCode(e);
|
||||
|
|
@ -157,7 +157,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
|||
function onPointerDown(e: PointerEvent) {
|
||||
const { target } = e;
|
||||
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;
|
||||
|
||||
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 WasmEditorInstance, type WasmRawInstance } from "@graphite/wasm-communication/editor";
|
||||
|
||||
import type MenuList from "@graphite/components/floating-menus/MenuList.svelte";
|
||||
|
||||
export class JsMessage {
|
||||
// The marker provides a way to check if an object is a sub-class constructor for a jsMessage.
|
||||
static readonly jsMessageMarker = true;
|
||||
|
|
@ -791,7 +793,7 @@ export type MenuBarEntry = MenuEntryCommon & {
|
|||
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 & {
|
||||
action?: () => void;
|
||||
children?: MenuListEntry[][];
|
||||
|
|
@ -801,7 +803,7 @@ export type MenuListEntry = MenuEntryCommon & {
|
|||
disabled?: boolean;
|
||||
tooltip?: string;
|
||||
font?: URL;
|
||||
ref?: any;
|
||||
ref?: MenuList;
|
||||
};
|
||||
|
||||
export class CurveManipulatorGroup {
|
||||
|
|
@ -1010,7 +1012,7 @@ export class Separator extends WidgetProps {
|
|||
type!: SeparatorType;
|
||||
}
|
||||
|
||||
export class SwatchPairInput extends WidgetProps {
|
||||
export class WorkingColorsButton extends WidgetProps {
|
||||
@Type(() => 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 class PivotAssist extends WidgetProps {
|
||||
export class PivotInput extends WidgetProps {
|
||||
position!: PivotPosition;
|
||||
|
||||
disabled!: boolean;
|
||||
|
|
@ -1141,11 +1143,11 @@ const widgetSubTypes = [
|
|||
{ value: NumberInput, name: "NumberInput" },
|
||||
{ value: OptionalInput, name: "OptionalInput" },
|
||||
{ value: ParameterExposeButton, name: "ParameterExposeButton" },
|
||||
{ value: PivotAssist, name: "PivotAssist" },
|
||||
{ value: PivotInput, name: "PivotInput" },
|
||||
{ value: PopoverButton, name: "PopoverButton" },
|
||||
{ value: RadioInput, name: "RadioInput" },
|
||||
{ value: Separator, name: "Separator" },
|
||||
{ value: SwatchPairInput, name: "SwatchPairInput" },
|
||||
{ value: WorkingColorsButton, name: "WorkingColorsButton" },
|
||||
{ value: TextAreaInput, name: "TextAreaInput" },
|
||||
{ value: TextButton, name: "TextButton" },
|
||||
{ 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 function isWidgetColumn(layoutColumn: LayoutGroup): layoutColumn is WidgetColumn {
|
||||
return Boolean((layoutColumn as WidgetColumn).columnWidgets);
|
||||
export type WidgetSpanColumn = { columnWidgets: Widget[] };
|
||||
export function isWidgetSpanColumn(layoutColumn: LayoutGroup): layoutColumn is WidgetSpanColumn {
|
||||
return Boolean((layoutColumn as WidgetSpanColumn).columnWidgets);
|
||||
}
|
||||
|
||||
export type WidgetRow = { rowWidgets: Widget[] };
|
||||
export function isWidgetRow(layoutRow: LayoutGroup): layoutRow is WidgetRow {
|
||||
return Boolean((layoutRow as WidgetRow).rowWidgets);
|
||||
export type WidgetSpanRow = { rowWidgets: Widget[] };
|
||||
export function isWidgetSpanRow(layoutRow: LayoutGroup): layoutRow is WidgetSpanRow {
|
||||
return Boolean((layoutRow as WidgetSpanRow).rowWidgets);
|
||||
}
|
||||
|
||||
export type WidgetSection = { name: string; layout: LayoutGroup[] };
|
||||
|
|
@ -1301,12 +1303,12 @@ function createLayoutGroup(layoutGroup: any): LayoutGroup {
|
|||
if (layoutGroup.column) {
|
||||
const columnWidgets = hoistWidgetHolders(layoutGroup.column.columnWidgets);
|
||||
|
||||
const result: WidgetColumn = { columnWidgets };
|
||||
const result: WidgetSpanColumn = { columnWidgets };
|
||||
return result;
|
||||
}
|
||||
|
||||
if (layoutGroup.row) {
|
||||
const result: WidgetRow = { rowWidgets: hoistWidgetHolders(layoutGroup.row.rowWidgets) };
|
||||
const result: WidgetSpanRow = { rowWidgets: hoistWidgetHolders(layoutGroup.row.rowWidgets) };
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ order = 1 # Page number after chapter intro
|
|||
- Interface
|
||||
- Title bar
|
||||
- Menu bar
|
||||
- Focused document title
|
||||
- Document title
|
||||
- Window buttons
|
||||
- Workspace
|
||||
- Panel interface (tab, pin, options bar, left menu)
|
||||
|
|
|
|||
Loading…
Reference in New Issue