Add suffix widget non-rounding; add disabled state to many widgets

This commit is contained in:
Keavon Chambers 2022-11-04 15:03:22 -07:00
parent ff75e0eae9
commit a1e061fa14
28 changed files with 218 additions and 63 deletions

View File

@ -85,6 +85,7 @@ impl PropertyHolder for ExportDialogMessageHandler {
WidgetHolder::new(Widget::RadioInput(RadioInput { WidgetHolder::new(Widget::RadioInput(RadioInput {
selected_index: self.file_type as u32, selected_index: self.file_type as u32,
entries, entries,
..Default::default()
})), })),
]; ];

View File

@ -9,6 +9,8 @@ use serde::{Deserialize, Serialize};
pub struct PivotAssist { pub struct PivotAssist {
pub position: PivotPosition, pub position: PivotPosition,
pub disabled: bool,
// Callbacks // Callbacks
#[serde(skip)] #[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")] #[derivative(Debug = "ignore", PartialEq = "ignore")]

View File

@ -11,6 +11,8 @@ pub struct IconButton {
pub size: u32, // TODO: Convert to an `IconSize` enum pub size: u32, // TODO: Convert to an `IconSize` enum
pub disabled: bool,
pub active: bool, pub active: bool,
pub tooltip: String, pub tooltip: String,
@ -29,9 +31,12 @@ pub struct IconButton {
pub struct PopoverButton { pub struct PopoverButton {
pub icon: Option<String>, pub icon: Option<String>,
// Body pub disabled: bool,
// Placeholder popover content heading
pub header: String, pub header: String,
// Placeholder popover content paragraph
pub text: String, pub text: String,
pub tooltip: String, pub tooltip: String,

View File

@ -238,6 +238,8 @@ pub struct OptionalInput {
pub struct RadioInput { pub struct RadioInput {
pub entries: Vec<RadioEntryData>, pub entries: Vec<RadioEntryData>,
pub disabled: bool,
// This uses `u32` instead of `usize` since it will be serialized as a normal JS number (replace this with `usize` after switching to a Rust-based GUI) // This uses `u32` instead of `usize` since it will be serialized as a normal JS number (replace this with `usize` after switching to a Rust-based GUI)
#[serde(rename = "selectedIndex")] #[serde(rename = "selectedIndex")]
pub selected_index: u32, pub selected_index: u32,

View File

@ -5,6 +5,8 @@ use serde::{Deserialize, Serialize};
pub struct IconLabel { pub struct IconLabel {
pub icon: String, pub icon: String,
pub disabled: bool,
pub tooltip: String, pub tooltip: String,
} }
@ -32,6 +34,8 @@ pub enum SeparatorType {
#[derive(Clone, Serialize, Deserialize, Derivative, Debug, PartialEq, Eq, Default)] #[derive(Clone, Serialize, Deserialize, Derivative, Debug, PartialEq, Eq, Default)]
pub struct TextLabel { pub struct TextLabel {
pub disabled: bool,
pub bold: bool, pub bold: bool,
pub italic: bool, pub italic: bool,

View File

@ -1564,6 +1564,7 @@ impl DocumentMessageHandler {
..RadioEntryData::default() ..RadioEntryData::default()
}, },
], ],
..Default::default()
})), })),
WidgetHolder::new(Widget::PopoverButton(PopoverButton { WidgetHolder::new(Widget::PopoverButton(PopoverButton {
header: "View Mode".into(), header: "View Mode".into(),

View File

@ -44,6 +44,7 @@ pub fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDequ
WidgetHolder::new(Widget::IconLabel(IconLabel { WidgetHolder::new(Widget::IconLabel(IconLabel {
icon: "NodeArtboard".into(), icon: "NodeArtboard".into(),
tooltip: "Artboard".into(), tooltip: "Artboard".into(),
..Default::default()
})), })),
WidgetHolder::new(Widget::Separator(Separator { WidgetHolder::new(Widget::Separator(Separator {
separator_type: SeparatorType::Related, separator_type: SeparatorType::Related,
@ -226,22 +227,27 @@ pub fn register_artwork_layer_properties(layer: &Layer, responses: &mut VecDeque
LayerDataType::Folder(_) => WidgetHolder::new(Widget::IconLabel(IconLabel { LayerDataType::Folder(_) => WidgetHolder::new(Widget::IconLabel(IconLabel {
icon: "NodeFolder".into(), icon: "NodeFolder".into(),
tooltip: "Folder".into(), tooltip: "Folder".into(),
..Default::default()
})), })),
LayerDataType::Shape(_) => WidgetHolder::new(Widget::IconLabel(IconLabel { LayerDataType::Shape(_) => WidgetHolder::new(Widget::IconLabel(IconLabel {
icon: "NodeShape".into(), icon: "NodeShape".into(),
tooltip: "Shape".into(), tooltip: "Shape".into(),
..Default::default()
})), })),
LayerDataType::Text(_) => WidgetHolder::new(Widget::IconLabel(IconLabel { LayerDataType::Text(_) => WidgetHolder::new(Widget::IconLabel(IconLabel {
icon: "NodeText".into(), icon: "NodeText".into(),
tooltip: "Text".into(), tooltip: "Text".into(),
..Default::default()
})), })),
LayerDataType::Image(_) => WidgetHolder::new(Widget::IconLabel(IconLabel { LayerDataType::Image(_) => WidgetHolder::new(Widget::IconLabel(IconLabel {
icon: "NodeImage".into(), icon: "NodeImage".into(),
tooltip: "Image".into(), tooltip: "Image".into(),
..Default::default()
})), })),
LayerDataType::Imaginate(_) => WidgetHolder::new(Widget::IconLabel(IconLabel { LayerDataType::Imaginate(_) => WidgetHolder::new(Widget::IconLabel(IconLabel {
icon: "NodeImaginate".into(), icon: "NodeImaginate".into(),
tooltip: "Imaginate".into(), tooltip: "Imaginate".into(),
..Default::default()
})), })),
}, },
WidgetHolder::new(Widget::Separator(Separator { WidgetHolder::new(Widget::Separator(Separator {
@ -338,6 +344,7 @@ fn node_section_transform(layer: &Layer, persistent_data: &PersistentData) -> La
WidgetHolder::new(Widget::PivotAssist(PivotAssist { WidgetHolder::new(Widget::PivotAssist(PivotAssist {
position: layer.pivot.into(), position: layer.pivot.into(),
on_update: WidgetCallback::new(|pivot_assist: &PivotAssist| PropertiesPanelMessage::SetPivot { new_position: pivot_assist.position }.into()), on_update: WidgetCallback::new(|pivot_assist: &PivotAssist| PropertiesPanelMessage::SetPivot { new_position: pivot_assist.position }.into()),
..Default::default()
})), })),
WidgetHolder::new(Widget::Separator(Separator { WidgetHolder::new(Widget::Separator(Separator {
separator_type: SeparatorType::Unrelated, separator_type: SeparatorType::Unrelated,
@ -1152,6 +1159,7 @@ fn node_gradient_type(gradient: &Gradient) -> LayoutGroup {
..RadioEntryData::default() ..RadioEntryData::default()
}, },
], ],
..Default::default()
})), })),
], ],
} }
@ -1371,6 +1379,7 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutGroup {
..RadioEntryData::default() ..RadioEntryData::default()
}, },
], ],
..Default::default()
})), })),
], ],
}, },
@ -1418,6 +1427,7 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutGroup {
..RadioEntryData::default() ..RadioEntryData::default()
}, },
], ],
..Default::default()
})), })),
], ],
}, },

View File

@ -129,6 +129,7 @@ impl PropertyHolder for GradientTool {
..RadioEntryData::default() ..RadioEntryData::default()
}, },
], ],
..Default::default()
}))], }))],
}])) }]))
} }

View File

@ -259,6 +259,7 @@ impl PropertyHolder for SelectTool {
WidgetHolder::new(Widget::PivotAssist(PivotAssist { WidgetHolder::new(Widget::PivotAssist(PivotAssist {
position: self.tool_data.pivot.to_pivot_position(), position: self.tool_data.pivot.to_pivot_position(),
on_update: WidgetCallback::new(|pivot_assist: &PivotAssist| SelectToolMessage::SetPivot { position: pivot_assist.position }.into()), on_update: WidgetCallback::new(|pivot_assist: &PivotAssist| SelectToolMessage::SetPivot { position: pivot_assist.position }.into()),
..Default::default()
})), })),
], ],
}])) }]))

View File

@ -183,9 +183,10 @@ impl PropertyHolder for ToolData {
WidgetHolder::new(Widget::IconButton(IconButton { WidgetHolder::new(Widget::IconButton(IconButton {
icon: icon_name, icon: icon_name,
size: 32, size: 32,
disabled: false,
active: self.active_tool_type == tool_type,
tooltip: tooltip.clone(), tooltip: tooltip.clone(),
tooltip_shortcut, tooltip_shortcut,
active: self.active_tool_type == tool_type,
on_update: WidgetCallback::new(move |_| { on_update: WidgetCallback::new(move |_| {
if !tooltip.contains("Coming Soon") { if !tooltip.contains("Coming Soon") {
ToolMessage::ActivateTool { tool_type }.into() ToolMessage::ActivateTool { tool_type }.into()

View File

@ -99,6 +99,11 @@ img {
display: block; display: block;
} }
.sharp-right-corners.sharp-right-corners.sharp-right-corners.sharp-right-corners {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.layout-row, .layout-row,
.layout-col { .layout-col {
.scrollable-x, .scrollable-x,

View File

@ -2,7 +2,7 @@
<template> <template>
<div class="widget-layout"> <div class="widget-layout">
<component :is="LayoutGroupType(layoutRow)" :widgetData="layoutRow" :layoutTarget="layout.layoutTarget" v-for="(layoutRow, index) in layout.layout" :key="index" /> <component :is="layoutGroupType(layoutRow)" :widgetData="layoutRow" :layoutTarget="layout.layoutTarget" v-for="(layoutRow, index) in layout.layout" :key="index" />
</div> </div>
</template> </template>
@ -28,7 +28,7 @@ export default defineComponent({
layout: { type: Object as PropType<WidgetLayout>, required: true }, layout: { type: Object as PropType<WidgetLayout>, required: true },
}, },
methods: { methods: {
LayoutGroupType(layoutRow: LayoutGroup): unknown { layoutGroupType(layoutRow: LayoutGroup): unknown {
if (isWidgetColumn(layoutRow)) return WidgetRow; if (isWidgetColumn(layoutRow)) return WidgetRow;
if (isWidgetRow(layoutRow)) return WidgetRow; if (isWidgetRow(layoutRow)) return WidgetRow;
if (isWidgetSection(layoutRow)) return WidgetSection; if (isWidgetSection(layoutRow)) return WidgetSection;
@ -36,10 +36,6 @@ export default defineComponent({
throw new Error("Layout row type does not exist"); throw new Error("Layout row type does not exist");
}, },
}, },
data: () => ({
isWidgetRow,
isWidgetSection,
}),
components: { components: {
WidgetRow, WidgetRow,
WidgetSection, WidgetSection,

View File

@ -3,17 +3,30 @@
<template> <template>
<div :class="`widget-${direction}`"> <div :class="`widget-${direction}`">
<template v-for="(component, index) in widgets" :key="index"> <template v-for="([component, nextIsSuffix], index) in widgetsAndNextSiblingIsSuffix" :key="index">
<CheckboxInput v-if="component.props.kind === 'CheckboxInput'" v-bind="component.props" @update:checked="(value: boolean) => updateLayout(component.widgetId, value)" /> <CheckboxInput v-if="component.props.kind === 'CheckboxInput'" v-bind="component.props" @update:checked="(value: boolean) => updateLayout(component.widgetId, value)" />
<ColorInput v-if="component.props.kind === 'ColorInput'" v-bind="component.props" v-model:open="open" @update:value="(value: unknown) => updateLayout(component.widgetId, value)" /> <ColorInput
v-if="component.props.kind === 'ColorInput'"
v-bind="component.props"
v-model:open="open"
@update:value="(value: unknown) => updateLayout(component.widgetId, value)"
:sharpRightCorners="nextIsSuffix"
/>
<DropdownInput <DropdownInput
v-if="component.props.kind === 'DropdownInput'" v-if="component.props.kind === 'DropdownInput'"
v-bind="component.props" v-bind="component.props"
v-model:open="open" v-model:open="open"
@update:selectedIndex="(value: number) => updateLayout(component.widgetId, value)" @update:selectedIndex="(value: number) => updateLayout(component.widgetId, value)"
:sharpRightCorners="nextIsSuffix"
/> />
<FontInput v-if="component.props.kind === 'FontInput'" v-bind="component.props" v-model:open="open" @changeFont="(value: unknown) => updateLayout(component.widgetId, value)" /> <FontInput
<IconButton v-if="component.props.kind === 'IconButton'" v-bind="component.props" :action="() => updateLayout(component.widgetId, undefined)" /> v-if="component.props.kind === 'FontInput'"
v-bind="component.props"
v-model:open="open"
@changeFont="(value: unknown) => updateLayout(component.widgetId, value)"
:sharpRightCorners="nextIsSuffix"
/>
<IconButton v-if="component.props.kind === 'IconButton'" v-bind="component.props" :action="() => updateLayout(component.widgetId, undefined)" :sharpRightCorners="nextIsSuffix" />
<IconLabel v-if="component.props.kind === 'IconLabel'" v-bind="component.props" /> <IconLabel v-if="component.props.kind === 'IconLabel'" v-bind="component.props" />
<NumberInput <NumberInput
v-if="component.props.kind === 'NumberInput'" v-if="component.props.kind === 'NumberInput'"
@ -21,6 +34,7 @@
@update:value="(value: number) => updateLayout(component.widgetId, value)" @update:value="(value: number) => updateLayout(component.widgetId, value)"
:incrementCallbackIncrease="() => updateLayout(component.widgetId, 'Increment')" :incrementCallbackIncrease="() => updateLayout(component.widgetId, 'Increment')"
:incrementCallbackDecrease="() => updateLayout(component.widgetId, 'Decrement')" :incrementCallbackDecrease="() => updateLayout(component.widgetId, 'Decrement')"
:sharpRightCorners="nextIsSuffix"
/> />
<OptionalInput v-if="component.props.kind === 'OptionalInput'" v-bind="component.props" @update:checked="(value: boolean) => updateLayout(component.widgetId, value)" /> <OptionalInput v-if="component.props.kind === 'OptionalInput'" v-bind="component.props" @update:checked="(value: boolean) => updateLayout(component.widgetId, value)" />
<PivotAssist v-if="component.props.kind === 'PivotAssist'" v-bind="component.props" @update:position="(value: string) => updateLayout(component.widgetId, value)" /> <PivotAssist v-if="component.props.kind === 'PivotAssist'" v-bind="component.props" @update:position="(value: string) => updateLayout(component.widgetId, value)" />
@ -28,12 +42,22 @@
<h3>{{ (component.props as any).header }}</h3> <h3>{{ (component.props as any).header }}</h3>
<p>{{ (component.props as any).text }}</p> <p>{{ (component.props as any).text }}</p>
</PopoverButton> </PopoverButton>
<RadioInput v-if="component.props.kind === 'RadioInput'" v-bind="component.props" @update:selectedIndex="(value: number) => updateLayout(component.widgetId, value)" /> <RadioInput
v-if="component.props.kind === 'RadioInput'"
v-bind="component.props"
@update:selectedIndex="(value: number) => updateLayout(component.widgetId, value)"
:sharpRightCorners="nextIsSuffix"
/>
<Separator v-if="component.props.kind === 'Separator'" v-bind="component.props" /> <Separator v-if="component.props.kind === 'Separator'" v-bind="component.props" />
<SwatchPairInput v-if="component.props.kind === 'SwatchPairInput'" v-bind="component.props" /> <SwatchPairInput v-if="component.props.kind === 'SwatchPairInput'" v-bind="component.props" />
<TextAreaInput v-if="component.props.kind === 'TextAreaInput'" v-bind="component.props" @commitText="(value: string) => updateLayout(component.widgetId, value)" /> <TextAreaInput v-if="component.props.kind === 'TextAreaInput'" v-bind="component.props" @commitText="(value: string) => updateLayout(component.widgetId, value)" />
<TextButton v-if="component.props.kind === 'TextButton'" v-bind="component.props" :action="() => updateLayout(component.widgetId, undefined)" /> <TextButton v-if="component.props.kind === 'TextButton'" v-bind="component.props" :action="() => updateLayout(component.widgetId, undefined)" :sharpRightCorners="nextIsSuffix" />
<TextInput v-if="component.props.kind === 'TextInput'" v-bind="component.props" @commitText="(value: string) => updateLayout(component.widgetId, value)" /> <TextInput
v-if="component.props.kind === 'TextInput'"
v-bind="component.props"
@commitText="(value: string) => updateLayout(component.widgetId, value)"
:sharpRightCorners="nextIsSuffix"
/>
<TextLabel v-if="component.props.kind === 'TextLabel'" v-bind="withoutValue(component.props)">{{ (component.props as any).value }}</TextLabel> <TextLabel v-if="component.props.kind === 'TextLabel'" v-bind="withoutValue(component.props)">{{ (component.props as any).value }}</TextLabel>
</template> </template>
</div> </div>
@ -74,6 +98,7 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, type PropType } from "vue"; import { defineComponent, type PropType } from "vue";
import type { Widget } from "@/wasm-communication/messages";
import { isWidgetColumn, isWidgetRow, type WidgetColumn, type WidgetRow } from "@/wasm-communication/messages"; import { isWidgetColumn, isWidgetRow, type WidgetColumn, type WidgetRow } from "@/wasm-communication/messages";
import PivotAssist from "@/components/widgets/assists/PivotAssist.vue"; import PivotAssist from "@/components/widgets/assists/PivotAssist.vue";
@ -94,6 +119,8 @@ import IconLabel from "@/components/widgets/labels/IconLabel.vue";
import Separator from "@/components/widgets/labels/Separator.vue"; import Separator from "@/components/widgets/labels/Separator.vue";
import TextLabel from "@/components/widgets/labels/TextLabel.vue"; import TextLabel from "@/components/widgets/labels/TextLabel.vue";
const SUFFIX_WIDGETS = ["PopoverButton"];
export default defineComponent({ export default defineComponent({
inject: ["editor"], inject: ["editor"],
props: { props: {
@ -106,15 +133,24 @@ export default defineComponent({
}; };
}, },
computed: { computed: {
direction() { direction(): "column" | "row" | "ERROR" {
if (isWidgetColumn(this.widgetData)) return "column"; if (isWidgetColumn(this.widgetData)) return "column";
if (isWidgetRow(this.widgetData)) return "row"; if (isWidgetRow(this.widgetData)) return "row";
return "ERROR"; return "ERROR";
}, },
widgets() { widgetsAndNextSiblingIsSuffix(): [Widget, boolean][] {
if (isWidgetColumn(this.widgetData)) return this.widgetData.columnWidgets; let widgets: Widget[] = [];
if (isWidgetRow(this.widgetData)) return this.widgetData.rowWidgets; if (isWidgetColumn(this.widgetData)) widgets = this.widgetData.columnWidgets;
return []; if (isWidgetRow(this.widgetData)) widgets = this.widgetData.rowWidgets;
return widgets.map((widget, index): [Widget, boolean] => {
// A suffix widget is one that joins up with this widget at the end with only a 1px gap.
// It uses the CSS sibling selector to give its own left edge corners zero radius.
// But this JS is needed to set its preceding sibling widget's right edge corners to zero radius.
const nextSiblingIsSuffix = SUFFIX_WIDGETS.includes(widgets[index + 1]?.props.kind);
return [widget, nextSiblingIsSuffix];
});
}, },
}, },
methods: { methods: {

View File

@ -1,14 +1,14 @@
<template> <template>
<div class="pivot-assist"> <div class="pivot-assist" :class="{ disabled }">
<button @click="setPosition('TopLeft')" class="row-1 col-1" :class="{ active: position === 'TopLeft' }" tabindex="-1"><div></div></button> <button @click="setPosition('TopLeft')" class="row-1 col-1" :class="{ active: position === 'TopLeft' }" tabindex="-1" :disabled="disabled"><div></div></button>
<button @click="setPosition('TopCenter')" class="row-1 col-2" :class="{ active: position === 'TopCenter' }" tabindex="-1"><div></div></button> <button @click="setPosition('TopCenter')" class="row-1 col-2" :class="{ active: position === 'TopCenter' }" tabindex="-1" :disabled="disabled"><div></div></button>
<button @click="setPosition('TopRight')" class="row-1 col-3" :class="{ active: position === 'TopRight' }" tabindex="-1"><div></div></button> <button @click="setPosition('TopRight')" class="row-1 col-3" :class="{ active: position === 'TopRight' }" tabindex="-1" :disabled="disabled"><div></div></button>
<button @click="setPosition('CenterLeft')" class="row-2 col-1" :class="{ active: position === 'CenterLeft' }" tabindex="-1"><div></div></button> <button @click="setPosition('CenterLeft')" class="row-2 col-1" :class="{ active: position === 'CenterLeft' }" tabindex="-1" :disabled="disabled"><div></div></button>
<button @click="setPosition('Center')" class="row-2 col-2" :class="{ active: position === 'Center' }" tabindex="-1"><div></div></button> <button @click="setPosition('Center')" class="row-2 col-2" :class="{ active: position === 'Center' }" tabindex="-1" :disabled="disabled"><div></div></button>
<button @click="setPosition('CenterRight')" class="row-2 col-3" :class="{ active: position === 'CenterRight' }" tabindex="-1"><div></div></button> <button @click="setPosition('CenterRight')" class="row-2 col-3" :class="{ active: position === 'CenterRight' }" tabindex="-1" :disabled="disabled"><div></div></button>
<button @click="setPosition('BottomLeft')" class="row-3 col-1" :class="{ active: position === 'BottomLeft' }" tabindex="-1"><div></div></button> <button @click="setPosition('BottomLeft')" class="row-3 col-1" :class="{ active: position === 'BottomLeft' }" tabindex="-1" :disabled="disabled"><div></div></button>
<button @click="setPosition('BottomCenter')" class="row-3 col-2" :class="{ active: position === 'BottomCenter' }" tabindex="-1"><div></div></button> <button @click="setPosition('BottomCenter')" class="row-3 col-2" :class="{ active: position === 'BottomCenter' }" tabindex="-1" :disabled="disabled"><div></div></button>
<button @click="setPosition('BottomRight')" class="row-3 col-3" :class="{ active: position === 'BottomRight' }" tabindex="-1"><div></div></button> <button @click="setPosition('BottomRight')" class="row-3 col-3" :class="{ active: position === 'BottomRight' }" tabindex="-1" :disabled="disabled"><div></div></button>
</div> </div>
</template> </template>
@ -18,6 +18,8 @@
flex: 0 0 auto; flex: 0 0 auto;
width: 24px; width: 24px;
height: 24px; height: 24px;
--pivot-border-color: var(--color-5-dullgray);
--pivot-fill-active: var(--color-e-nearwhite);
button { button {
position: absolute; position: absolute;
@ -26,16 +28,11 @@
margin: 0; margin: 0;
padding: 0; padding: 0;
background: var(--color-1-nearblack); background: var(--color-1-nearblack);
border: 1px solid var(--color-5-dullgray); border: 1px solid var(--pivot-border-color);
&:hover {
border-color: transparent;
background: var(--color-6-lowergray);
}
&.active { &.active {
border-color: transparent; border-color: transparent;
background: var(--color-e-nearwhite); background: var(--pivot-fill-active);
} }
&.col-1::before, &.col-1::before,
@ -44,7 +41,7 @@
pointer-events: none; pointer-events: none;
width: 2px; width: 2px;
height: 0; height: 0;
border-top: 1px solid var(--color-5-dullgray); border-top: 1px solid var(--pivot-border-color);
position: absolute; position: absolute;
top: 1px; top: 1px;
right: -3px; right: -3px;
@ -56,7 +53,7 @@
pointer-events: none; pointer-events: none;
width: 0; width: 0;
height: 2px; height: 2px;
border-left: 1px solid var(--color-5-dullgray); border-left: 1px solid var(--pivot-border-color);
position: absolute; position: absolute;
bottom: -3px; bottom: -3px;
right: 1px; right: 1px;
@ -91,6 +88,16 @@
margin: -2px; margin: -2px;
} }
} }
&:not(.disabled) button:not(.active):hover {
border-color: transparent;
background: var(--color-6-lowergray);
}
&.disabled button {
--pivot-border-color: var(--color-4-dimgray);
--pivot-fill-active: var(--color-8-uppergray);
}
} }
</style> </style>
@ -103,6 +110,7 @@ export default defineComponent({
emits: ["update:position"], emits: ["update:position"],
props: { props: {
position: { type: String as PropType<string>, required: true }, position: { type: String as PropType<string>, required: true },
disabled: { type: Boolean as PropType<boolean>, default: false },
}, },
methods: { methods: {
setPosition(newPosition: PivotPosition) { setPosition(newPosition: PivotPosition) {

View File

@ -1,5 +1,11 @@
<template> <template>
<button :class="['icon-button', `size-${size}`, active && 'active']" @click="(e: MouseEvent) => action(e)" :title="tooltip" :tabindex="active ? -1 : 0"> <button
:class="['icon-button', `size-${size}`, { disabled, active, 'sharp-right-corners': sharpRightCorners }]"
@click="(e: MouseEvent) => action(e)"
:disabled="disabled"
:title="tooltip"
:tabindex="active ? -1 : 0"
>
<IconLabel :icon="icon" /> <IconLabel :icon="icon" />
</button> </button>
</template> </template>
@ -24,15 +30,7 @@
margin-left: 0; margin-left: 0;
} }
&.active { &:hover {
background: var(--color-e-nearwhite);
svg {
fill: var(--color-2-mildblack);
}
}
&:hover:not(.active) {
background: var(--color-6-lowergray); background: var(--color-6-lowergray);
color: var(--color-f-white); color: var(--color-f-white);
@ -41,6 +39,22 @@
} }
} }
&.disabled {
background: none;
svg {
fill: var(--color-8-uppergray);
}
}
&.active {
background: var(--color-e-nearwhite);
svg {
fill: var(--color-2-mildblack);
}
}
&.size-12 { &.size-12 {
width: 12px; width: 12px;
height: 12px; height: 12px;
@ -74,8 +88,10 @@ export default defineComponent({
props: { props: {
icon: { type: String as PropType<IconName>, required: true }, icon: { type: String as PropType<IconName>, required: true },
size: { type: Number as PropType<IconSize>, required: true }, size: { type: Number as PropType<IconSize>, required: true },
disabled: { type: Boolean as PropType<boolean>, default: false },
active: { type: Boolean as PropType<boolean>, default: false }, active: { type: Boolean as PropType<boolean>, default: false },
tooltip: { type: String as PropType<string | undefined>, required: false }, tooltip: { type: String as PropType<string | undefined>, required: false },
sharpRightCorners: { type: Boolean as PropType<boolean>, default: false },
// Callbacks // Callbacks
action: { type: Function as PropType<(e?: MouseEvent) => void>, required: true }, action: { type: Function as PropType<(e?: MouseEvent) => void>, required: true },

View File

@ -1,6 +1,6 @@
<template> <template>
<LayoutRow class="popover-button"> <LayoutRow class="popover-button">
<IconButton :class="{ open }" :action="() => onClick()" :icon="icon" :size="16" data-floating-menu-spawner :tooltip="tooltip" /> <IconButton :class="{ open }" :disabled="disabled" :action="() => onClick()" :icon="icon" :size="16" data-floating-menu-spawner :tooltip="tooltip" />
<FloatingMenu v-model:open="open" :type="'Popover'" :direction="'Bottom'"> <FloatingMenu v-model:open="open" :type="'Popover'" :direction="'Bottom'">
<slot></slot> <slot></slot>
</FloatingMenu> </FloatingMenu>
@ -33,6 +33,11 @@
background: var(--color-6-lowergray); background: var(--color-6-lowergray);
fill: var(--color-f-white); fill: var(--color-f-white);
} }
&.disabled {
background: var(--color-2-mildblack);
fill: var(--color-8-uppergray);
}
} }
// TODO: Refactor this and other complicated cases dealing with joined widget margins and border-radius by adding a single standard set of classes: joined-first, joined-inner, and joined-last // TODO: Refactor this and other complicated cases dealing with joined widget margins and border-radius by adding a single standard set of classes: joined-first, joined-inner, and joined-last
@ -59,6 +64,7 @@ export default defineComponent({
props: { props: {
icon: { type: String as PropType<IconName>, default: "DropdownArrow" }, icon: { type: String as PropType<IconName>, default: "DropdownArrow" },
tooltip: { type: String as PropType<string | undefined>, required: false }, tooltip: { type: String as PropType<string | undefined>, required: false },
disabled: { type: Boolean as PropType<boolean>, default: false },
// Callbacks // Callbacks
action: { type: Function as PropType<() => void>, required: false }, action: { type: Function as PropType<() => void>, required: false },

View File

@ -1,7 +1,7 @@
<template> <template>
<button <button
class="text-button" class="text-button"
:class="{ emphasized, disabled }" :class="{ emphasized, disabled, 'sharp-right-corners': sharpRightCorners }"
:data-emphasized="emphasized || undefined" :data-emphasized="emphasized || undefined"
:data-disabled="disabled || undefined" :data-disabled="disabled || undefined"
data-text-button data-text-button
@ -80,6 +80,7 @@ export default defineComponent({
minWidth: { type: Number as PropType<number>, default: 0 }, minWidth: { type: Number as PropType<number>, default: 0 },
disabled: { type: Boolean as PropType<boolean>, default: false }, disabled: { type: Boolean as PropType<boolean>, default: false },
tooltip: { type: String as PropType<string | undefined>, required: false }, tooltip: { type: String as PropType<string | undefined>, required: false },
sharpRightCorners: { type: Boolean as PropType<boolean>, default: false },
// Callbacks // Callbacks
action: { type: Function as PropType<(e: MouseEvent) => void>, required: true }, action: { type: Function as PropType<(e: MouseEvent) => void>, required: true },

View File

@ -1,6 +1,12 @@
<template> <template>
<LayoutRow class="color-input" :title="tooltip"> <LayoutRow class="color-input" :class="{ 'sharp-right-corners': sharpRightCorners }" :title="tooltip">
<button :class="{ none: value.none }" :style="{ '--chosen-color': value.toHexOptionalAlpha() }" @click="() => $emit('update:open', true)" tabindex="0" data-floating-menu-spawner> <button
:class="{ none: value.none, 'sharp-right-corners': sharpRightCorners }"
:style="{ '--chosen-color': value.toHexOptionalAlpha() }"
@click="() => $emit('update:open', true)"
tabindex="0"
data-floating-menu-spawner
>
<TextLabel :bold="true" class="chip" v-if="chip">{{ chip }}</TextLabel> <TextLabel :bold="true" class="chip" v-if="chip">{{ chip }}</TextLabel>
</button> </button>
<ColorPicker v-model:open="isOpen" :color="value" @update:color="(color: Color) => colorPickerUpdated(color)" :allowNone="true" /> <ColorPicker v-model:open="isOpen" :color="value" @update:color="(color: Color) => colorPickerUpdated(color)" :allowNone="true" />
@ -86,8 +92,9 @@ export default defineComponent({
props: { props: {
value: { type: Color as PropType<Color>, required: true }, value: { type: Color as PropType<Color>, required: true },
noTransparency: { type: Boolean as PropType<boolean>, default: false }, // TODO: Rename to allowTransparency, also implement allowNone noTransparency: { type: Boolean as PropType<boolean>, default: false }, // TODO: Rename to allowTransparency, also implement allowNone
disabled: { type: Boolean as PropType<boolean>, default: false }, disabled: { type: Boolean as PropType<boolean>, default: false }, // TODO: Design and implement
tooltip: { type: String as PropType<string | undefined>, required: false }, tooltip: { type: String as PropType<string | undefined>, required: false },
sharpRightCorners: { type: Boolean as PropType<boolean>, default: false },
// Bound through `v-model` // Bound through `v-model`
// TODO: See if this should be made to follow the pattern of DropdownInput.vue so this could be removed // TODO: See if this should be made to follow the pattern of DropdownInput.vue so this could be removed

View File

@ -2,7 +2,7 @@
<LayoutRow class="dropdown-input" data-dropdown-input> <LayoutRow class="dropdown-input" data-dropdown-input>
<LayoutRow <LayoutRow
class="dropdown-box" class="dropdown-box"
:class="{ disabled, open }" :class="{ disabled, open, 'sharp-right-corners': sharpRightCorners }"
:style="{ minWidth: `${minWidth}px` }" :style="{ minWidth: `${minWidth}px` }"
:title="tooltip" :title="tooltip"
@click="() => !disabled && (open = true)" @click="() => !disabled && (open = true)"
@ -116,6 +116,7 @@ export default defineComponent({
interactive: { type: Boolean as PropType<boolean>, default: true }, interactive: { type: Boolean as PropType<boolean>, default: true },
disabled: { type: Boolean as PropType<boolean>, default: false }, disabled: { type: Boolean as PropType<boolean>, default: false },
tooltip: { type: String as PropType<string | undefined>, required: false }, tooltip: { type: String as PropType<string | undefined>, required: false },
sharpRightCorners: { type: Boolean as PropType<boolean>, default: false },
}, },
data() { data() {
return { return {

View File

@ -1,6 +1,6 @@
<!-- This is a base component, extended by others like NumberInput and TextInput. It should not be used directly. --> <!-- This is a base component, extended by others like NumberInput and TextInput. It should not be used directly. -->
<template> <template>
<LayoutRow class="field-input" :class="{ disabled }" :title="tooltip"> <LayoutRow class="field-input" :class="{ disabled, 'sharp-right-corners': sharpRightCorners }" :title="tooltip">
<input <input
v-if="!textarea" v-if="!textarea"
:class="{ 'has-label': label }" :class="{ 'has-label': label }"
@ -136,6 +136,7 @@ export default defineComponent({
disabled: { type: Boolean as PropType<boolean>, default: false }, disabled: { type: Boolean as PropType<boolean>, default: false },
textarea: { type: Boolean as PropType<boolean>, default: false }, textarea: { type: Boolean as PropType<boolean>, default: false },
tooltip: { type: String as PropType<string | undefined>, required: false }, tooltip: { type: String as PropType<string | undefined>, required: false },
sharpRightCorners: { type: Boolean as PropType<boolean>, default: false },
}, },
data() { data() {
return { return {

View File

@ -1,8 +1,10 @@
<!-- TODO: Combine this widget into the DropdownInput widget -->
<template> <template>
<LayoutRow class="font-input"> <LayoutRow class="font-input">
<LayoutRow <LayoutRow
class="dropdown-box" class="dropdown-box"
:class="{ disabled }" :class="{ disabled, 'sharp-right-corners': sharpRightCorners }"
:style="{ minWidth: `${minWidth}px` }" :style="{ minWidth: `${minWidth}px` }"
:title="tooltip" :title="tooltip"
:tabindex="disabled ? -1 : 0" :tabindex="disabled ? -1 : 0"
@ -95,6 +97,7 @@ export default defineComponent({
isStyle: { type: Boolean as PropType<boolean>, default: false }, isStyle: { type: Boolean as PropType<boolean>, default: false },
disabled: { type: Boolean as PropType<boolean>, default: false }, disabled: { type: Boolean as PropType<boolean>, default: false },
tooltip: { type: String as PropType<string | undefined>, required: false }, tooltip: { type: String as PropType<string | undefined>, required: false },
sharpRightCorners: { type: Boolean as PropType<boolean>, default: false },
}, },
data() { data() {
return { return {

View File

@ -7,6 +7,7 @@
:disabled="disabled" :disabled="disabled"
:style="minWidth > 0 ? `min-width: ${minWidth}px` : ''" :style="minWidth > 0 ? `min-width: ${minWidth}px` : ''"
:tooltip="tooltip" :tooltip="tooltip"
:sharpRightCorners="sharpRightCorners"
@textFocused="() => onTextFocused()" @textFocused="() => onTextFocused()"
@textChanged="() => onTextChanged()" @textChanged="() => onTextChanged()"
@cancelTextChange="() => onCancelTextChange()" @cancelTextChange="() => onCancelTextChange()"
@ -110,6 +111,7 @@ export default defineComponent({
disabled: { type: Boolean as PropType<boolean>, default: false }, disabled: { type: Boolean as PropType<boolean>, default: false },
minWidth: { type: Number as PropType<number>, default: 0 }, minWidth: { type: Number as PropType<number>, default: 0 },
tooltip: { type: String as PropType<string | undefined>, required: false }, tooltip: { type: String as PropType<string | undefined>, required: false },
sharpRightCorners: { type: Boolean as PropType<boolean>, default: false },
// Callbacks // Callbacks
incrementCallbackIncrease: { type: Function as PropType<() => void>, required: false }, incrementCallbackIncrease: { type: Function as PropType<() => void>, required: false },

View File

@ -1,12 +1,13 @@
<template> <template>
<LayoutRow class="radio-input"> <LayoutRow class="radio-input" :class="{ disabled }">
<button <button
:class="{ active: index === selectedIndex }" :class="{ active: index === selectedIndex, disabled, 'sharp-right-corners': index === entries.length - 1 && sharpRightCorners }"
v-for="(entry, index) in entries" v-for="(entry, index) in entries"
:key="index" :key="index"
@click="() => handleEntryClick(entry)" @click="() => handleEntryClick(entry)"
:title="entry.tooltip" :title="entry.tooltip"
:tabindex="index === selectedIndex ? -1 : 0" :tabindex="index === selectedIndex ? -1 : 0"
:disabled="disabled"
> >
<IconLabel v-if="entry.icon" :icon="entry.icon" /> <IconLabel v-if="entry.icon" :icon="entry.icon" />
<TextLabel v-if="entry.label">{{ entry.label }}</TextLabel> <TextLabel v-if="entry.label">{{ entry.label }}</TextLabel>
@ -43,6 +44,24 @@
} }
} }
&.disabled {
background: var(--color-4-dimgray);
color: var(--color-8-uppergray);
svg {
fill: var(--color-8-uppergray);
}
&.active {
background: var(--color-8-uppergray);
color: var(--color-2-mildblack);
svg {
fill: var(--color-2-mildblack);
}
}
}
& + button { & + button {
margin-left: 1px; margin-left: 1px;
} }
@ -80,7 +99,9 @@ export default defineComponent({
emits: ["update:selectedIndex"], emits: ["update:selectedIndex"],
props: { props: {
entries: { type: Array as PropType<RadioEntries>, required: true }, entries: { type: Array as PropType<RadioEntries>, required: true },
disabled: { type: Boolean as PropType<boolean>, default: false },
selectedIndex: { type: Number as PropType<number>, required: true }, selectedIndex: { type: Number as PropType<number>, required: true },
sharpRightCorners: { type: Boolean as PropType<boolean>, default: false },
}, },
methods: { methods: {
handleEntryClick(radioEntryData: RadioEntryData) { handleEntryClick(radioEntryData: RadioEntryData) {

View File

@ -8,6 +8,7 @@
:disabled="disabled" :disabled="disabled"
:tooltip="tooltip" :tooltip="tooltip"
:style="minWidth > 0 ? `min-width: ${minWidth}px` : ''" :style="minWidth > 0 ? `min-width: ${minWidth}px` : ''"
:sharpRightCorners="sharpRightCorners"
@textFocused="() => onTextFocused()" @textFocused="() => onTextFocused()"
@textChanged="() => onTextChanged()" @textChanged="() => onTextChanged()"
@cancelTextChange="() => onCancelTextChange()" @cancelTextChange="() => onCancelTextChange()"
@ -43,6 +44,7 @@ export default defineComponent({
centered: { type: Boolean as PropType<boolean>, default: false }, centered: { type: Boolean as PropType<boolean>, default: false },
minWidth: { type: Number as PropType<number>, default: 0 }, minWidth: { type: Number as PropType<number>, default: 0 },
tooltip: { type: String as PropType<string | undefined>, required: false }, tooltip: { type: String as PropType<string | undefined>, required: false },
sharpRightCorners: { type: Boolean as PropType<boolean>, default: false },
}, },
data() { data() {
return { return {

View File

@ -1,5 +1,5 @@
<template> <template>
<LayoutRow :class="['icon-label', iconSizeClass]" :title="tooltip"> <LayoutRow :class="['icon-label', iconSizeClass, { disabled }]" :title="tooltip">
<component :is="icon" /> <component :is="icon" />
</LayoutRow> </LayoutRow>
</template> </template>
@ -9,6 +9,10 @@
flex: 0 0 auto; flex: 0 0 auto;
fill: var(--color-e-nearwhite); fill: var(--color-e-nearwhite);
&.disabled {
fill: var(--color-8-uppergray);
}
&.size-12 { &.size-12 {
width: 12px; width: 12px;
height: 12px; height: 12px;
@ -36,6 +40,7 @@ import LayoutRow from "@/components/layout/LayoutRow.vue";
export default defineComponent({ export default defineComponent({
props: { props: {
icon: { type: String as PropType<IconName>, required: true }, icon: { type: String as PropType<IconName>, required: true },
disabled: { type: Boolean as PropType<boolean>, default: false },
tooltip: { type: String as PropType<string | undefined>, required: false }, tooltip: { type: String as PropType<string | undefined>, required: false },
}, },
computed: { computed: {

View File

@ -1,5 +1,5 @@
<template> <template>
<span class="text-label" :class="{ bold, italic, multiline, 'table-align': tableAlign }" :style="minWidth > 0 ? `min-width: ${minWidth}px` : ''" :title="tooltip"> <span class="text-label" :class="{ disabled, bold, italic, multiline, 'table-align': tableAlign }" :style="minWidth > 0 ? `min-width: ${minWidth}px` : ''" :title="tooltip">
<slot></slot> <slot></slot>
</span> </span>
</template> </template>
@ -9,6 +9,10 @@
line-height: 18px; line-height: 18px;
white-space: nowrap; white-space: nowrap;
&.disabled {
color: var(--color-8-uppergray);
}
&.bold { &.bold {
font-weight: 700; font-weight: 700;
} }
@ -34,6 +38,7 @@ import { defineComponent, type PropType } from "vue";
export default defineComponent({ export default defineComponent({
props: { props: {
disabled: { type: Boolean as PropType<boolean>, default: false },
bold: { type: Boolean as PropType<boolean>, default: false }, bold: { type: Boolean as PropType<boolean>, default: false },
italic: { type: Boolean as PropType<boolean>, default: false }, italic: { type: Boolean as PropType<boolean>, default: false },
tableAlign: { type: Boolean as PropType<boolean>, default: false }, tableAlign: { type: Boolean as PropType<boolean>, default: false },

View File

@ -26,8 +26,8 @@ export function createPanicManager(editor: Editor, dialogState: DialogState): vo
function preparePanicDialog(header: string, details: string, panicDetails: string): [IconName, WidgetLayout, TextButtonWidget[]] { function preparePanicDialog(header: string, details: string, panicDetails: string): [IconName, WidgetLayout, TextButtonWidget[]] {
const widgets: WidgetLayout = { const widgets: WidgetLayout = {
layout: [ layout: [
{ rowWidgets: [new Widget({ kind: "TextLabel", value: header, bold: true, italic: false, tableAlign: false, minWidth: 0, multiline: false, tooltip: "" }, 0n)] }, { rowWidgets: [new Widget({ kind: "TextLabel", value: header, disabled: false, bold: true, italic: false, tableAlign: false, minWidth: 0, multiline: false, tooltip: "" }, 0n)] },
{ rowWidgets: [new Widget({ kind: "TextLabel", value: details, bold: false, italic: false, tableAlign: false, minWidth: 0, multiline: true, tooltip: "" }, 1n)] }, { rowWidgets: [new Widget({ kind: "TextLabel", value: details, disabled: false, bold: false, italic: false, tableAlign: false, minWidth: 0, multiline: true, tooltip: "" }, 1n)] },
], ],
layoutTarget: undefined, layoutTarget: undefined,
}; };

View File

@ -788,6 +788,8 @@ export class IconButton extends WidgetProps {
size!: IconSize; size!: IconSize;
disabled!: boolean;
active!: boolean; active!: boolean;
@Transform(({ value }: { value: string }) => (value.length > 0 ? value : undefined)) @Transform(({ value }: { value: string }) => (value.length > 0 ? value : undefined))
@ -797,6 +799,8 @@ export class IconButton extends WidgetProps {
export class IconLabel extends WidgetProps { export class IconLabel extends WidgetProps {
icon!: IconName; icon!: IconName;
disabled!: boolean;
@Transform(({ value }: { value: string }) => (value.length > 0 ? value : undefined)) @Transform(({ value }: { value: string }) => (value.length > 0 ? value : undefined))
tooltip!: string | undefined; tooltip!: string | undefined;
} }
@ -846,6 +850,8 @@ export class OptionalInput extends WidgetProps {
export class PopoverButton extends WidgetProps { export class PopoverButton extends WidgetProps {
icon!: string | undefined; icon!: string | undefined;
disabled!: boolean;
// Body // Body
header!: string; header!: string;
@ -869,6 +875,8 @@ export type RadioEntries = RadioEntryData[];
export class RadioInput extends WidgetProps { export class RadioInput extends WidgetProps {
entries!: RadioEntries; entries!: RadioEntries;
disabled!: boolean;
selectedIndex!: number; selectedIndex!: number;
} }
@ -951,6 +959,8 @@ export class TextLabel extends WidgetProps {
value!: string; value!: string;
// Props // Props
disabled!: boolean;
bold!: boolean; bold!: boolean;
italic!: boolean; italic!: boolean;
@ -969,6 +979,8 @@ export type PivotPosition = "None" | "TopLeft" | "TopCenter" | "TopRight" | "Cen
export class PivotAssist extends WidgetProps { export class PivotAssist extends WidgetProps {
position!: PivotPosition; position!: PivotPosition;
disabled!: boolean;
} }
// WIDGET // WIDGET