mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-30 02:38:11 +08:00
@@ -125,6 +125,12 @@ impl<F: Fn(&MessageDiscriminant) -> Vec<KeysGroup>> MessageHandler<LayoutMessage
|
|||||||
let callback_message = (optional_input.on_update.callback)(optional_input);
|
let callback_message = (optional_input.on_update.callback)(optional_input);
|
||||||
responses.push_back(callback_message);
|
responses.push_back(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);
|
||||||
|
responses.push_back(callback_message);
|
||||||
|
}
|
||||||
Widget::PopoverButton(_) => {}
|
Widget::PopoverButton(_) => {}
|
||||||
Widget::RadioInput(radio_input) => {
|
Widget::RadioInput(radio_input) => {
|
||||||
let update_value = value.as_u64().expect("RadioInput update was not of type: u64");
|
let update_value = value.as_u64().expect("RadioInput update was not of type: u64");
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use super::widgets::assist_widgets::*;
|
||||||
use super::widgets::button_widgets::*;
|
use super::widgets::button_widgets::*;
|
||||||
use super::widgets::input_widgets::*;
|
use super::widgets::input_widgets::*;
|
||||||
use super::widgets::label_widgets::*;
|
use super::widgets::label_widgets::*;
|
||||||
@@ -278,6 +279,7 @@ pub enum Widget {
|
|||||||
InvisibleStandinInput(InvisibleStandinInput),
|
InvisibleStandinInput(InvisibleStandinInput),
|
||||||
NumberInput(NumberInput),
|
NumberInput(NumberInput),
|
||||||
OptionalInput(OptionalInput),
|
OptionalInput(OptionalInput),
|
||||||
|
PivotAssist(PivotAssist),
|
||||||
PopoverButton(PopoverButton),
|
PopoverButton(PopoverButton),
|
||||||
RadioInput(RadioInput),
|
RadioInput(RadioInput),
|
||||||
Separator(Separator),
|
Separator(Separator),
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
use derivative::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::messages::layout::utility_types::layout_widget::WidgetCallback;
|
||||||
|
|
||||||
|
#[derive(Clone, Default, Derivative, Serialize, Deserialize)]
|
||||||
|
#[derivative(Debug, PartialEq)]
|
||||||
|
pub struct PivotAssist {
|
||||||
|
pub position: PivotPosition,
|
||||||
|
|
||||||
|
// Callbacks
|
||||||
|
#[serde(skip)]
|
||||||
|
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||||
|
pub on_update: WidgetCallback<PivotAssist>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq, Eq)]
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
pub mod assist_widgets;
|
||||||
pub mod button_widgets;
|
pub mod button_widgets;
|
||||||
pub mod input_widgets;
|
pub mod input_widgets;
|
||||||
pub mod label_widgets;
|
pub mod label_widgets;
|
||||||
|
|||||||
+3
-3
@@ -62,7 +62,7 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Don't allow grab with no selected layers
|
// Don't allow grab with no selected layers
|
||||||
if selected_layers.len() == 0 {
|
if selected_layers.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Don't allow rotate with no selected layers
|
// Don't allow rotate with no selected layers
|
||||||
if selected_layers.len() == 0 {
|
if selected_layers.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Don't allow scale with no selected layers
|
// Don't allow scale with no selected layers
|
||||||
if selected_layers.len() == 0 {
|
if selected_layers.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ impl SelectedEdges {
|
|||||||
_ => size,
|
_ => size,
|
||||||
};
|
};
|
||||||
let delta_size = new_size - size;
|
let delta_size = new_size - size;
|
||||||
min = min - delta_size * min_pivot;
|
min -= delta_size * min_pivot;
|
||||||
max = min + new_size;
|
max = min + new_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use crate::messages::frontend::utility_types::MouseCursorIcon;
|
|||||||
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion};
|
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion};
|
||||||
use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;
|
use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;
|
||||||
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
||||||
|
use crate::messages::layout::utility_types::widgets::assist_widgets::{PivotAssist, PivotPosition};
|
||||||
use crate::messages::layout::utility_types::widgets::button_widgets::{IconButton, PopoverButton};
|
use crate::messages::layout::utility_types::widgets::button_widgets::{IconButton, PopoverButton};
|
||||||
use crate::messages::layout::utility_types::widgets::label_widgets::{Separator, SeparatorDirection, SeparatorType};
|
use crate::messages::layout::utility_types::widgets::label_widgets::{Separator, SeparatorDirection, SeparatorType};
|
||||||
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis};
|
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis};
|
||||||
@@ -245,6 +246,19 @@ impl PropertyHolder for SelectTool {
|
|||||||
text: "The contents of this popover menu are coming soon".into(),
|
text: "The contents of this popover menu are coming soon".into(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})),
|
})),
|
||||||
|
WidgetHolder::new(Widget::Separator(Separator {
|
||||||
|
direction: SeparatorDirection::Horizontal,
|
||||||
|
separator_type: SeparatorType::Section,
|
||||||
|
})),
|
||||||
|
// We'd like this widget to hide and show itself whenever the transformation cage is active or inactive (i.e. when no layers are selected)
|
||||||
|
WidgetHolder::new(Widget::PivotAssist(PivotAssist {
|
||||||
|
position: PivotPosition::Center,
|
||||||
|
on_update: WidgetCallback::new(|pivot_assist: &PivotAssist| {
|
||||||
|
// TODO: Make this actually do something
|
||||||
|
log::debug!("Changed pivot to {:?}", pivot_assist.position);
|
||||||
|
Message::NoOp
|
||||||
|
}),
|
||||||
|
})),
|
||||||
],
|
],
|
||||||
}]))
|
}]))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
:incrementCallbackDecrease="() => updateLayout(component.widgetId, 'Decrement')"
|
:incrementCallbackDecrease="() => updateLayout(component.widgetId, 'Decrement')"
|
||||||
/>
|
/>
|
||||||
<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)" />
|
||||||
<PopoverButton v-if="component.props.kind === 'PopoverButton'" v-bind="component.props">
|
<PopoverButton v-if="component.props.kind === 'PopoverButton'" v-bind="component.props">
|
||||||
<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>
|
||||||
@@ -75,6 +76,7 @@ import { defineComponent, type PropType } from "vue";
|
|||||||
|
|
||||||
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 IconButton from "@/components/widgets/buttons/IconButton.vue";
|
import IconButton from "@/components/widgets/buttons/IconButton.vue";
|
||||||
import PopoverButton from "@/components/widgets/buttons/PopoverButton.vue";
|
import PopoverButton from "@/components/widgets/buttons/PopoverButton.vue";
|
||||||
import TextButton from "@/components/widgets/buttons/TextButton.vue";
|
import TextButton from "@/components/widgets/buttons/TextButton.vue";
|
||||||
@@ -134,6 +136,7 @@ export default defineComponent({
|
|||||||
IconLabel,
|
IconLabel,
|
||||||
NumberInput,
|
NumberInput,
|
||||||
OptionalInput,
|
OptionalInput,
|
||||||
|
PivotAssist,
|
||||||
PopoverButton,
|
PopoverButton,
|
||||||
RadioInput,
|
RadioInput,
|
||||||
Separator,
|
Separator,
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
<template>
|
||||||
|
<div class="pivot-assist">
|
||||||
|
<button @click="setPosition('TopLeft')" class="row-1 col-1" :class="{ active: position === 'TopLeft' }"></button>
|
||||||
|
<button @click="setPosition('TopCenter')" class="row-1 col-2" :class="{ active: position === 'TopCenter' }"><div></div></button>
|
||||||
|
<button @click="setPosition('TopRight')" class="row-1 col-3" :class="{ active: position === 'TopRight' }"><div></div></button>
|
||||||
|
<button @click="setPosition('CenterLeft')" class="row-2 col-1" :class="{ active: position === 'CenterLeft' }"><div></div></button>
|
||||||
|
<button @click="setPosition('Center')" class="row-2 col-2" :class="{ active: position === 'Center' }"><div></div></button>
|
||||||
|
<button @click="setPosition('CenterRight')" class="row-2 col-3" :class="{ active: position === 'CenterRight' }"><div></div></button>
|
||||||
|
<button @click="setPosition('BottomLeft')" class="row-3 col-1" :class="{ active: position === 'BottomLeft' }"><div></div></button>
|
||||||
|
<button @click="setPosition('BottomCenter')" class="row-3 col-2" :class="{ active: position === 'BottomCenter' }"><div></div></button>
|
||||||
|
<button @click="setPosition('BottomRight')" class="row-3 col-3" :class="{ active: position === 'BottomRight' }"><div></div></button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.pivot-assist {
|
||||||
|
position: relative;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
position: absolute;
|
||||||
|
width: 5px;
|
||||||
|
height: 5px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
outline: none;
|
||||||
|
background: none;
|
||||||
|
border: 1px solid var(--color-7-middlegray);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: transparent;
|
||||||
|
background: var(--color-6-lowergray);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
border-color: transparent;
|
||||||
|
background: var(--color-f-white);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.col-1::before,
|
||||||
|
&.col-2::before {
|
||||||
|
content: "";
|
||||||
|
pointer-events: none;
|
||||||
|
width: 2px;
|
||||||
|
height: 0;
|
||||||
|
border-top: 1px solid var(--color-7-middlegray);
|
||||||
|
position: absolute;
|
||||||
|
top: 1px;
|
||||||
|
right: -3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.row-1::after,
|
||||||
|
&.row-2::after {
|
||||||
|
content: "";
|
||||||
|
pointer-events: none;
|
||||||
|
width: 0;
|
||||||
|
height: 2px;
|
||||||
|
border-left: 1px solid var(--color-7-middlegray);
|
||||||
|
position: absolute;
|
||||||
|
bottom: -3px;
|
||||||
|
right: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.row-1 {
|
||||||
|
top: 3px;
|
||||||
|
}
|
||||||
|
&.col-1 {
|
||||||
|
left: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.row-2 {
|
||||||
|
top: 10px;
|
||||||
|
}
|
||||||
|
&.col-2 {
|
||||||
|
left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.row-3 {
|
||||||
|
top: 17px;
|
||||||
|
}
|
||||||
|
&.col-3 {
|
||||||
|
left: 17px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Click targets that extend 1px beyond the borders of each square
|
||||||
|
div {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 2px;
|
||||||
|
margin: -2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, type PropType } from "vue";
|
||||||
|
|
||||||
|
import { type PivotPosition } from "@/wasm-communication/messages";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
emits: ["update:position"],
|
||||||
|
props: {
|
||||||
|
position: { type: String as PropType<string>, required: true },
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setPosition(newPosition: PivotPosition) {
|
||||||
|
this.$emit("update:position", newPosition);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -612,6 +612,12 @@ export class TextLabel extends WidgetProps {
|
|||||||
multiline!: boolean;
|
multiline!: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type PivotPosition = "None" | "TopLeft" | "TopCenter" | "TopRight" | "CenterLeft" | "Center" | "CenterRight" | "BottomLeft" | "BottomCenter" | "BottomRight";
|
||||||
|
|
||||||
|
export class PivotAssist extends WidgetProps {
|
||||||
|
position!: PivotPosition;
|
||||||
|
}
|
||||||
|
|
||||||
// WIDGET
|
// WIDGET
|
||||||
|
|
||||||
const widgetSubTypes = [
|
const widgetSubTypes = [
|
||||||
@@ -631,6 +637,7 @@ const widgetSubTypes = [
|
|||||||
{ value: TextButton, name: "TextButton" },
|
{ value: TextButton, name: "TextButton" },
|
||||||
{ value: TextInput, name: "TextInput" },
|
{ value: TextInput, name: "TextInput" },
|
||||||
{ value: TextLabel, name: "TextLabel" },
|
{ value: TextLabel, name: "TextLabel" },
|
||||||
|
{ value: PivotAssist, name: "PivotAssist" },
|
||||||
];
|
];
|
||||||
export type WidgetPropsSet = InstanceType<typeof widgetSubTypes[number]["value"]>;
|
export type WidgetPropsSet = InstanceType<typeof widgetSubTypes[number]["value"]>;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user