Skip to content

Commit 62c9513

Browse files
committed
[IMP] renderer: add cell animations
This commit adds animations in the grid's canvas when a cell is changed. The animations are defined in the `cell_animation_registry.ts` and includes: - fade in the cell when it gets a new content. - fate out the cell when its content is removed. - text sliding animation when the text is changed. - color gradient animation when the cell colors is changed - and more The feature is designed to not impact the current grid drawing logic. We still have a first step yo create `Box` from the model content, and a second step to draw the `Box` in the canvas. The animations are a step in between, where we change the `Box` depending on the animation progress, and possibly add some additional `Box` to be drawn. Task: 4805149
1 parent 0969ddd commit 62c9513

27 files changed

+1607
-77
lines changed

src/components/grid_overlay/grid_overlay.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Component, onMounted, onWillUnmount, useExternalListener, useRef } from "@odoo/owl";
2-
import { deepEquals } from "../../helpers";
2+
import { deepEquals, positionToZone } from "../../helpers";
33
import { isPointInsideRect } from "../../helpers/rectangle";
44
import { Store, useStore } from "../../store_engine";
55
import {
@@ -299,7 +299,11 @@ export class GridOverlay extends Component<Props, SpreadsheetChildEnv> {
299299

300300
const icons = this.env.model.getters.getCellIcons(position);
301301
const icon = icons.find((icon) => {
302-
return isPointInsideRect(x, y, this.env.model.getters.getCellIconRect(icon));
302+
const merge = this.env.model.getters.getMerge(position);
303+
const zone = merge || positionToZone(position);
304+
const cellRect = this.env.model.getters.getRect(zone);
305+
306+
return isPointInsideRect(x, y, this.env.model.getters.getCellIconRect(icon, cellRect));
303307
});
304308
return icon?.onClick ? icon : undefined;
305309
}

src/components/helpers/draw_grid_hook.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Model } from "../../model";
44
import { useStore } from "../../store_engine";
55
import { GridRenderer } from "../../stores/grid_renderer_store";
66
import { RendererStore } from "../../stores/renderer_store";
7-
import { DOMDimension, OrderedLayers } from "../../types";
7+
import { DOMDimension } from "../../types";
88

99
export function useGridDrawing(refName: string, model: Model, canvasSize: () => DOMDimension) {
1010
const canvasRef = useRef(refName);
@@ -40,9 +40,6 @@ export function useGridDrawing(refName: string, model: Model, canvasSize: () =>
4040
ctx.translate(-CANVAS_SHIFT, -CANVAS_SHIFT);
4141
ctx.scale(dpr, dpr);
4242

43-
for (const layer of OrderedLayers()) {
44-
model.drawLayer(renderingContext, layer);
45-
rendererStore.drawLayer(renderingContext, layer);
46-
}
43+
rendererStore.draw(renderingContext);
4744
}
4845
}

src/components/icons/icons.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export type IconSetType = keyof typeof ICON_SETS;
4141
// We need here the svg of the icons that we need to convert to images for the renderer
4242
// -----------------------------------------------------------------------------
4343
const ARROW_DOWN: ImageSVG = {
44+
name: "ARROW_DOWN",
4445
width: 448,
4546
height: 512,
4647
paths: [
@@ -51,6 +52,7 @@ const ARROW_DOWN: ImageSVG = {
5152
],
5253
};
5354
const ARROW_UP: ImageSVG = {
55+
name: "ARROW_UP",
5456
width: 448,
5557
height: 512,
5658
paths: [
@@ -61,6 +63,7 @@ const ARROW_UP: ImageSVG = {
6163
],
6264
};
6365
const ARROW_RIGHT: ImageSVG = {
66+
name: "ARROW_RIGHT",
6467
width: 448,
6568
height: 512,
6669
paths: [
@@ -72,6 +75,7 @@ const ARROW_RIGHT: ImageSVG = {
7275
};
7376

7477
const SMILE: ImageSVG = {
78+
name: "SMILE",
7579
width: 496,
7680
height: 512,
7781
paths: [
@@ -82,6 +86,7 @@ const SMILE: ImageSVG = {
8286
],
8387
};
8488
const MEH: ImageSVG = {
89+
name: "MEH",
8590
width: 496,
8691
height: 512,
8792
paths: [
@@ -92,6 +97,7 @@ const MEH: ImageSVG = {
9297
],
9398
};
9499
const FROWN: ImageSVG = {
100+
name: "FROWN",
95101
width: 496,
96102
height: 512,
97103
paths: [
@@ -104,28 +110,33 @@ const FROWN: ImageSVG = {
104110

105111
const DOT_PATH = "M256 9 a247 247 0 1 0.1 0 0";
106112
const GREEN_DOT: ImageSVG = {
113+
name: "GREEN_DOT",
107114
width: 512,
108115
height: 512,
109116
paths: [{ fillColor: "#6AA84F", path: DOT_PATH }],
110117
};
111118
const YELLOW_DOT: ImageSVG = {
119+
name: "YELLOW_DOT",
112120
width: 512,
113121
height: 512,
114122
paths: [{ fillColor: "#F0AD4E", path: DOT_PATH }],
115123
};
116124
const RED_DOT: ImageSVG = {
125+
name: "RED_DOT",
117126
width: 512,
118127
height: 512,
119128
paths: [{ fillColor: "#E06666", path: DOT_PATH }],
120129
};
121130

122131
export const CARET_DOWN: ImageSVG = {
132+
name: "CARET_DOWN",
123133
width: 512,
124134
height: 512,
125135
paths: [{ fillColor: TEXT_BODY_MUTED, path: "M120 195 h270 l-135 130" }],
126136
};
127137

128138
export const HOVERED_CARET_DOWN: ImageSVG = {
139+
name: "CARET_DOWN",
129140
width: 512,
130141
height: 512,
131142
paths: [
@@ -135,18 +146,21 @@ export const HOVERED_CARET_DOWN: ImageSVG = {
135146
};
136147

137148
export const CHECKBOX_UNCHECKED: ImageSVG = {
149+
name: "CHECKBOX_UNCHECKED",
138150
width: 512,
139151
height: 512,
140152
paths: [{ fillColor: GRAY_300, path: "M45,45 h422 v422 h-422 v-422 m30,30 v362 h362 v-362" }],
141153
};
142154

143155
export const CHECKBOX_UNCHECKED_HOVERED: ImageSVG = {
156+
name: "CHECKBOX_UNCHECKED",
144157
width: 512,
145158
height: 512,
146159
paths: [{ fillColor: ACTION_COLOR, path: "M45,45 h422 v422 h-422 v-422 m30,30 v362 h362 v-362" }],
147160
};
148161

149162
export const CHECKBOX_CHECKED: ImageSVG = {
163+
name: "CHECKBOX_CHECKED",
150164
width: 512,
151165
height: 512,
152166
paths: [
@@ -161,6 +175,7 @@ export function getPivotIconSvg(isCollapsed: boolean, isHovered: boolean): Image
161175
: "M149,235 h213 v43 h-213"; // -
162176

163177
return {
178+
name: "PIVOT_ICON",
164179
width: 512,
165180
height: 512,
166181
paths: [
@@ -193,6 +208,7 @@ export function getDataFilterIcon(
193208
}
194209

195210
return {
211+
name: "DATA_FILTER_ICON",
196212
width: isActive ? 24 : 850,
197213
height: isActive ? 24 : 850,
198214
paths: [

src/components/side_panel/settings/settings_panel.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@ import { Component, onWillStart } from "@odoo/owl";
22
import { GRAY_100, GRAY_300 } from "../../../constants";
33
import { DAYS, deepEquals, formatValue } from "../../../helpers";
44
import { getDateTimeFormat, isValidLocale } from "../../../helpers/locale";
5+
import { Store, useStore } from "../../../store_engine";
6+
import { GridRenderer } from "../../../stores/grid_renderer_store";
7+
import { _t } from "../../../translation";
58
import { Locale, LocaleCode, SpreadsheetChildEnv } from "../../../types";
69
import { css } from "../../helpers";
710
import { ValidationMessages } from "../../validation_messages/validation_messages";
11+
import { BadgeSelection } from "../components/badge_selection/badge_selection";
812
import { Section } from "../components/section/section";
913

1014
interface Props {
@@ -20,12 +24,14 @@ css/* scss */ `
2024

2125
export class SettingsPanel extends Component<Props, SpreadsheetChildEnv> {
2226
static template = "o-spreadsheet-SettingsPanel";
23-
static components = { Section, ValidationMessages };
27+
static components = { Section, ValidationMessages, BadgeSelection };
2428
static props = { onCloseSidePanel: Function };
2529

2630
loadedLocales: Locale[] = [];
31+
gridRendererStore!: Store<GridRenderer>;
2732

2833
setup() {
34+
this.gridRendererStore = useStore(GridRenderer);
2935
onWillStart(() => this.loadLocales());
3036
}
3137

@@ -91,4 +97,19 @@ export class SettingsPanel extends Component<Props, SpreadsheetChildEnv> {
9197

9298
return this.loadedLocales;
9399
}
100+
101+
get cellAnimationChoices() {
102+
return [
103+
{ value: "on", label: _t("On") },
104+
{ value: "off", label: _t("Off") },
105+
];
106+
}
107+
108+
updateCellAnimations(value: "on" | "off") {
109+
this.env.model.dispatch("TOGGLE_CELL_ANIMATIONS", { disableCellAnimations: value === "off" });
110+
}
111+
112+
get cellAnimationValue() {
113+
return this.env.model.getters.areCellAnimationDisabled() ? "off" : "on";
114+
}
94115
}

src/components/side_panel/settings/settings_panel.xml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,17 @@
3131
</div>
3232
</div>
3333
</Section>
34+
35+
<Section title.translate="Cell animations">
36+
<BadgeSelection
37+
choices="cellAnimationChoices"
38+
onChange.bind="updateCellAnimations"
39+
selectedValue="cellAnimationValue"
40+
/>
41+
</Section>
42+
3443
<Section class="'pt-0'">
35-
<t t-set="message">This setting affects all users.</t>
44+
<t t-set="message">Those settings affect all users.</t>
3645
<ValidationMessages messages="[message]" msgType="'info'"/>
3746
</Section>
3847
</div>

src/plugins/core/settings.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ import {
1010
import { CorePlugin } from "./../core_plugin";
1111

1212
export class SettingsPlugin extends CorePlugin {
13-
static getters = ["getLocale"] as const;
13+
static getters = ["getLocale", "areCellAnimationDisabled"] as const;
1414
private locale: Locale = DEFAULT_LOCALE;
15+
private disableCellAnimations: boolean | undefined = undefined;
1516

1617
allowDispatch(cmd: CoreCommand) {
1718
switch (cmd.type) {
@@ -29,13 +30,20 @@ export class SettingsPlugin extends CorePlugin {
2930
this.history.update("locale", newLocale);
3031
this.changeCellsDateFormatWithLocale(oldLocale, newLocale);
3132
break;
33+
case "TOGGLE_CELL_ANIMATIONS":
34+
this.history.update("disableCellAnimations", cmd.disableCellAnimations);
35+
break;
3236
}
3337
}
3438

3539
getLocale(): Locale {
3640
return this.locale;
3741
}
3842

43+
areCellAnimationDisabled(): boolean {
44+
return !!this.disableCellAnimations;
45+
}
46+
3947
private changeCellsDateFormatWithLocale(oldLocale: Locale, newLocale: Locale) {
4048
for (const sheetId of this.getters.getSheetIds()) {
4149
for (const [cellId, cell] of Object.entries(this.getters.getCells(sheetId))) {
@@ -64,11 +72,13 @@ export class SettingsPlugin extends CorePlugin {
6472

6573
import(data: WorkbookData) {
6674
this.locale = data.settings?.locale ?? DEFAULT_LOCALE;
75+
this.disableCellAnimations = data.settings?.disableCellAnimations;
6776
}
6877

6978
export(data: WorkbookData) {
7079
data.settings = {
7180
locale: this.locale,
81+
disableCellAnimations: this.disableCellAnimations,
7282
};
7383
}
7484
}

src/plugins/ui_core_views/cell_icon_plugin.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isDefined, positionToZone } from "../../helpers/index";
1+
import { isDefined } from "../../helpers/index";
22
import {
33
GridIcon,
44
IconsOfCell,
@@ -33,11 +33,8 @@ export class CellIconPlugin extends CoreViewPlugin {
3333
return this.cellIconsCache[position.sheetId][position.col][position.row];
3434
}
3535

36-
getCellIconRect(icon: GridIcon): Rect {
36+
getCellIconRect(icon: GridIcon, cellRect: Rect): Rect {
3737
const cellPosition = icon.position;
38-
const merge = this.getters.getMerge(cellPosition);
39-
const zone = merge || positionToZone(cellPosition);
40-
const cellRect = this.getters.getRect(zone);
4138
const cell = this.getters.getCell(cellPosition);
4239

4340
const x = this.getIconHorizontalPosition(cellRect, icon.horizontalAlign, icon);

0 commit comments

Comments
 (0)