添加形状渐变功能

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;
}
export interface ShapeGradient {
type: 'line' | 'radial';
color: [string, string];
rotate: number;
}
export interface PPTShapeElement {
type: 'shape';
id: string;
@ -85,6 +90,7 @@ export interface PPTShapeElement {
path: string;
fixedRatio: boolean;
fill: string;
gradient?: ShapeGradient;
rotate?: number;
outline?: PPTElementOutline;
opacity?: number;

View File

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

View File

@ -1,17 +1,72 @@
<template>
<div class="shape-style-panel">
<div class="row">
<div style="flex: 2;">填充颜色</div>
<Popover trigger="click">
<Select
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>
<ColorPicker
:modelValue="fill"
@update:modelValue="value => updateFill(value)"
/>
</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>
</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 />
<ElementOutline />
@ -26,7 +81,7 @@
import { computed, defineComponent, ref, Ref, watch } from 'vue'
import { useStore } from 'vuex'
import { MutationTypes, State } from '@/store'
import { PPTShapeElement } from '@/types/slides'
import { PPTShapeElement, ShapeGradient } from '@/types/slides'
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
import ElementOpacity from '../common/ElementOpacity.vue'
@ -47,14 +102,40 @@ export default defineComponent({
const handleElement: Ref<PPTShapeElement> = computed(() => store.getters.handleElement)
const fill = ref<string>()
const gradient = ref<ShapeGradient>()
const fillType = ref('fill')
watch(handleElement, () => {
if(!handleElement.value) return
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 })
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 props = { fill: value }
store.commit(MutationTypes.UPDATE_ELEMENT, { id: handleElement.value.id, props })
@ -63,7 +144,11 @@ export default defineComponent({
return {
fill,
gradient,
fillType,
updateFillType,
updateFill,
updateGradient,
}
},
})

View File

@ -20,6 +20,15 @@
:width="elementInfo.width"
: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
: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-linejoin=""
:d="elementInfo.path"
:fill="elementInfo.fill"
:fill="elementInfo.gradient ? `url(#base-gradient-${elementInfo.id})` : elementInfo.fill"
:stroke="outlineColor"
:stroke-width="outlineWidth"
: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 useElementShadow from '@/views/components/element/hooks/useElementShadow'
import GradientDefs from './GradientDefs.vue'
export default defineComponent({
name: 'base-element-shape',
components: {
GradientDefs,
},
props: {
elementInfo: {
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"
: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
: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-linejoin=""
:d="elementInfo.path"
:fill="elementInfo.fill"
:fill="elementInfo.gradient ? `url(#editabel-gradient-${elementInfo.id})` : elementInfo.fill"
:stroke="outlineColor"
:stroke-width="outlineWidth"
: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 useElementShadow from '@/views/components/element/hooks/useElementShadow'
import GradientDefs from './GradientDefs.vue'
export default defineComponent({
name: 'editable-element-shape',
components: {
GradientDefs,
},
props: {
elementInfo: {
type: Object as PropType<PPTShapeElement>,