添加形状渐变功能

This commit is contained in:
pipipi-pikachu 2021-01-20 18:02:11 +08:00
parent 81f48438b1
commit 99171297df
6 changed files with 177 additions and 9 deletions

View File

@ -72,6 +72,11 @@ export interface PPTImageElement {
shadow?: PPTElementShadow; shadow?: PPTElementShadow;
} }
export interface ShapeGradient {
type: 'line' | 'radial';
color: [string, string];
rotate: number;
}
export interface PPTShapeElement { export interface PPTShapeElement {
type: 'shape'; type: 'shape';
id: string; id: string;
@ -85,6 +90,7 @@ export interface PPTShapeElement {
path: string; path: string;
fixedRatio: boolean; fixedRatio: boolean;
fill: string; fill: string;
gradient?: ShapeGradient;
rotate?: number; rotate?: number;
outline?: PPTElementOutline; outline?: PPTElementOutline;
opacity?: number; opacity?: number;

View File

@ -53,7 +53,7 @@
</div> </div>
<div class="row"> <div class="row">
<div style="flex: 2;">主题配色</div> <div style="flex: 2;">主题配色</div>
<Popover trigger="click" v-model:visible="themePoolVisible"> <Popover trigger="click" placement="bottom" v-model:visible="themePoolVisible">
<template #content> <template #content>
<div class="theme-pool"> <div class="theme-pool">
<div <div
@ -257,11 +257,12 @@ export default defineComponent({
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 5px 20px; margin: 0 -12px;
padding: 5px 32px;
transition: background-color .1s; transition: background-color .1s;
& + .theme-item { & + .theme-item {
margin-top: 8px; margin-top: 3px;
} }
&:hover { &:hover {

View File

@ -1,17 +1,72 @@
<template> <template>
<div class="shape-style-panel"> <div class="shape-style-panel">
<div class="row"> <div class="row">
<div style="flex: 2;">填充颜色</div> <Select
<Popover trigger="click"> style="flex: 10;"
:value="fillType"
@change="value => updateFillType(value)"
>
<SelectOption value="fill">纯色填充</SelectOption>
<SelectOption value="gradient">渐变填充</SelectOption>
</Select>
<div style="flex: 1;"></div>
<Popover trigger="click" v-if="fillType === 'fill'">
<template #content> <template #content>
<ColorPicker <ColorPicker
:modelValue="fill" :modelValue="fill"
@update:modelValue="value => updateFill(value)" @update:modelValue="value => updateFill(value)"
/> />
</template> </template>
<ColorButton :color="fill" style="flex: 3;" /> <ColorButton :color="fill" style="flex: 10;" />
</Popover>
<Select
style="flex: 10;"
:value="gradient.type"
@change="value => updateGradient({ type: value })"
v-else
>
<SelectOption value="line">线性渐变</SelectOption>
<SelectOption value="radial">径向渐变</SelectOption>
</Select>
</div>
<template v-if="fillType === 'gradient'">
<div class="row">
<div style="flex: 2;">起点颜色</div>
<Popover trigger="click">
<template #content>
<ColorPicker
:modelValue="gradient.color[0]"
@update:modelValue="value => updateGradient({ color: [value, gradient.color[1]] })"
/>
</template>
<ColorButton :color="gradient.color[0]" style="flex: 3;" />
</Popover> </Popover>
</div> </div>
<div class="row">
<div style="flex: 2;">终点颜色</div>
<Popover trigger="click">
<template #content>
<ColorPicker
:modelValue="gradient.color[1]"
@update:modelValue="value => updateGradient({ color: [gradient.color[0], value] })"
/>
</template>
<ColorButton :color="gradient.color[1]" style="flex: 3;" />
</Popover>
</div>
<div class="row" v-if="gradient.type === 'line'">
<div style="flex: 2;">渐变角度</div>
<Slider
:min="0"
:max="360"
:step="15"
:value="gradient.rotate"
style="flex: 3;"
@change="value => updateGradient({ rotate: value })"
/>
</div>
</template>
<Divider /> <Divider />
<ElementOutline /> <ElementOutline />
@ -26,7 +81,7 @@
import { computed, defineComponent, ref, Ref, watch } from 'vue' import { computed, defineComponent, ref, Ref, watch } from 'vue'
import { useStore } from 'vuex' import { useStore } from 'vuex'
import { MutationTypes, State } from '@/store' import { MutationTypes, State } from '@/store'
import { PPTShapeElement } from '@/types/slides' import { PPTShapeElement, ShapeGradient } from '@/types/slides'
import useHistorySnapshot from '@/hooks/useHistorySnapshot' import useHistorySnapshot from '@/hooks/useHistorySnapshot'
import ElementOpacity from '../common/ElementOpacity.vue' import ElementOpacity from '../common/ElementOpacity.vue'
@ -47,14 +102,40 @@ export default defineComponent({
const handleElement: Ref<PPTShapeElement> = computed(() => store.getters.handleElement) const handleElement: Ref<PPTShapeElement> = computed(() => store.getters.handleElement)
const fill = ref<string>() const fill = ref<string>()
const gradient = ref<ShapeGradient>()
const fillType = ref('fill')
watch(handleElement, () => { watch(handleElement, () => {
if(!handleElement.value) return if(!handleElement.value) return
fill.value = handleElement.value.fill || '#000' fill.value = handleElement.value.fill || '#000'
gradient.value = handleElement.value.gradient || { type: 'line', rotate: 0, color: [fill.value, '#fff'] }
fillType.value = handleElement.value.gradient ? 'gradient' : 'fill'
}, { deep: true, immediate: true }) }, { deep: true, immediate: true })
const { addHistorySnapshot } = useHistorySnapshot() const { addHistorySnapshot } = useHistorySnapshot()
const updateFillType = (type: 'gradient' | 'fill') => {
if(type === 'fill') {
store.commit(MutationTypes.REMOVE_ELEMENT_PROPS, {
id: handleElement.value.id,
propName: 'gradient',
})
}
else {
const props = { gradient: gradient.value }
store.commit(MutationTypes.UPDATE_ELEMENT, { id: handleElement.value.id, props })
}
addHistorySnapshot()
}
const updateGradient = (gradientProps: Partial<ShapeGradient>) => {
const props = { gradient: { ...gradient.value, ...gradientProps } }
store.commit(MutationTypes.UPDATE_ELEMENT, { id: handleElement.value.id, props })
addHistorySnapshot()
}
const updateFill = (value: string) => { const updateFill = (value: string) => {
const props = { fill: value } const props = { fill: value }
store.commit(MutationTypes.UPDATE_ELEMENT, { id: handleElement.value.id, props }) store.commit(MutationTypes.UPDATE_ELEMENT, { id: handleElement.value.id, props })
@ -63,7 +144,11 @@ export default defineComponent({
return { return {
fill, fill,
gradient,
fillType,
updateFillType,
updateFill, updateFill,
updateGradient,
} }
}, },
}) })

View File

@ -20,6 +20,15 @@
:width="elementInfo.width" :width="elementInfo.width"
:height="elementInfo.height" :height="elementInfo.height"
> >
<defs v-if="elementInfo.gradient">
<GradientDefs
:id="`base-gradient-${elementInfo.id}`"
:type="elementInfo.gradient.type"
:color1="elementInfo.gradient.color[0]"
:color2="elementInfo.gradient.color[1]"
:rotate="elementInfo.gradient.rotate"
/>
</defs>
<g <g
:transform="`scale(${elementInfo.width / elementInfo.viewBox}, ${elementInfo.height / elementInfo.viewBox}) translate(0,0) matrix(1,0,0,1,0,0)`" :transform="`scale(${elementInfo.width / elementInfo.viewBox}, ${elementInfo.height / elementInfo.viewBox}) translate(0,0) matrix(1,0,0,1,0,0)`"
> >
@ -29,7 +38,7 @@
stroke-miterlimit="8" stroke-miterlimit="8"
stroke-linejoin="" stroke-linejoin=""
:d="elementInfo.path" :d="elementInfo.path"
:fill="elementInfo.fill" :fill="elementInfo.gradient ? `url(#base-gradient-${elementInfo.id})` : elementInfo.fill"
:stroke="outlineColor" :stroke="outlineColor"
:stroke-width="outlineWidth" :stroke-width="outlineWidth"
:stroke-dasharray="outlineStyle === 'dashed' ? '10 5' : '0 0'" :stroke-dasharray="outlineStyle === 'dashed' ? '10 5' : '0 0'"
@ -46,8 +55,13 @@ import { PPTShapeElement } from '@/types/slides'
import useElementOutline from '@/views/components/element/hooks/useElementOutline' import useElementOutline from '@/views/components/element/hooks/useElementOutline'
import useElementShadow from '@/views/components/element/hooks/useElementShadow' import useElementShadow from '@/views/components/element/hooks/useElementShadow'
import GradientDefs from './GradientDefs.vue'
export default defineComponent({ export default defineComponent({
name: 'base-element-shape', name: 'base-element-shape',
components: {
GradientDefs,
},
props: { props: {
elementInfo: { elementInfo: {
type: Object as PropType<PPTShapeElement>, type: Object as PropType<PPTShapeElement>,

View File

@ -0,0 +1,48 @@
<template>
<linearGradient
v-if="type === 'line'"
:id="id"
x1="0%"
y1="0%"
x2="100%"
y2="0%"
:gradientTransform="`rotate(${rotate},0.5,0.5)`"
>
<stop offset="0%" :stop-color="color1" />
<stop offset="100%" :stop-color="color2" />
</linearGradient>
<radialGradient :id="id" v-else>
<stop offset="0%" :stop-color="color1" />
<stop offset="100%" :stop-color="color2" />
</radialGradient>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue'
export default defineComponent({
name: 'gradient-defs',
props: {
id: {
type: String,
required: true,
},
type: {
type: String as PropType<'line' | 'radial'>,
},
color1: {
type: String,
required: true,
},
color2: {
type: String,
required: true,
},
rotate: {
type: Number,
default: 0,
},
},
})
</script>

View File

@ -23,6 +23,15 @@
:width="elementInfo.width" :width="elementInfo.width"
:height="elementInfo.height" :height="elementInfo.height"
> >
<defs v-if="elementInfo.gradient">
<GradientDefs
:id="`editabel-gradient-${elementInfo.id}`"
:type="elementInfo.gradient.type"
:color1="elementInfo.gradient.color[0]"
:color2="elementInfo.gradient.color[1]"
:rotate="elementInfo.gradient.rotate"
/>
</defs>
<g <g
:transform="`scale(${elementInfo.width / elementInfo.viewBox}, ${elementInfo.height / elementInfo.viewBox}) translate(0,0) matrix(1,0,0,1,0,0)`" :transform="`scale(${elementInfo.width / elementInfo.viewBox}, ${elementInfo.height / elementInfo.viewBox}) translate(0,0) matrix(1,0,0,1,0,0)`"
> >
@ -32,7 +41,7 @@
stroke-miterlimit="8" stroke-miterlimit="8"
stroke-linejoin="" stroke-linejoin=""
:d="elementInfo.path" :d="elementInfo.path"
:fill="elementInfo.fill" :fill="elementInfo.gradient ? `url(#editabel-gradient-${elementInfo.id})` : elementInfo.fill"
:stroke="outlineColor" :stroke="outlineColor"
:stroke-width="outlineWidth" :stroke-width="outlineWidth"
:stroke-dasharray="outlineStyle === 'dashed' ? '10 5' : '0 0'" :stroke-dasharray="outlineStyle === 'dashed' ? '10 5' : '0 0'"
@ -50,8 +59,13 @@ import { ContextmenuItem } from '@/components/Contextmenu/types'
import useElementOutline from '@/views/components/element/hooks/useElementOutline' import useElementOutline from '@/views/components/element/hooks/useElementOutline'
import useElementShadow from '@/views/components/element/hooks/useElementShadow' import useElementShadow from '@/views/components/element/hooks/useElementShadow'
import GradientDefs from './GradientDefs.vue'
export default defineComponent({ export default defineComponent({
name: 'editable-element-shape', name: 'editable-element-shape',
components: {
GradientDefs,
},
props: { props: {
elementInfo: { elementInfo: {
type: Object as PropType<PPTShapeElement>, type: Object as PropType<PPTShapeElement>,