diff --git a/gallery/src/pages/components/ha-control-circular-slider.ts b/gallery/src/pages/components/ha-control-circular-slider.ts index 8c6fca52d4..f85f210dd0 100644 --- a/gallery/src/pages/components/ha-control-circular-slider.ts +++ b/gallery/src/pages/components/ha-control-circular-slider.ts @@ -150,17 +150,13 @@ export class DemoHaCircularSlider extends LitElement { } ha-control-circular-slider { --control-circular-slider-color: #ff9800; - --control-circular-slider-background: #ff9800; - --control-circular-slider-background-opacity: 0.3; } ha-control-circular-slider[inverted] { --control-circular-slider-color: #2196f3; - --control-circular-slider-background: #2196f3; } ha-control-circular-slider[dual] { --control-circular-slider-high-color: #2196f3; --control-circular-slider-low-color: #ff9800; - --control-circular-slider-background: var(--disabled-color); } .field { display: flex; diff --git a/src/components/ha-control-circular-slider.ts b/src/components/ha-control-circular-slider.ts index 5b3caf3bef..ffe26f17dc 100644 --- a/src/components/ha-control-circular-slider.ts +++ b/src/components/ha-control-circular-slider.ts @@ -18,10 +18,9 @@ import { import { customElement, property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { ifDefined } from "lit/directives/if-defined"; -import { styleMap } from "lit/directives/style-map"; import { fireEvent } from "../common/dom/fire_event"; import { clamp } from "../common/number/clamp"; -import { arc } from "../resources/svg-arc"; +import { svgArc } from "../resources/svg-arc"; const MAX_ANGLE = 270; const ROTATE_ANGLE = 360 - MAX_ANGLE / 2 - 90; @@ -388,44 +387,130 @@ export class HaControlCircularSlider extends LitElement { } } - private _strokeDashArc( - percentage: number, - inverted?: boolean - ): [string, string] { - const maxRatio = MAX_ANGLE / 360; - const f = RADIUS * 2 * Math.PI; - if (inverted) { - const arcLength = (1 - percentage) * f * maxRatio; - const strokeDasharray = `${arcLength} ${f - arcLength}`; - const strokeDashOffset = `${arcLength + f * (1 - maxRatio)}`; - return [strokeDasharray, strokeDashOffset]; - } - const arcLength = percentage * f * maxRatio; - const strokeDasharray = `${arcLength} ${f - arcLength}`; - const strokeDashOffset = "0"; + private _strokeCircleDashArc(value: number): [string, string] { + return this._strokeDashArc(value, value); + } + + private _strokeDashArc(from: number, to: number): [string, string] { + const start = this._valueToPercentage(from); + const end = this._valueToPercentage(to); + + const track = (RADIUS * 2 * Math.PI * MAX_ANGLE) / 360; + const arc = Math.max((end - start) * track, 0); + const arcOffset = start * track - 0.5; + + const strokeDasharray = `${arc} ${track - arc}`; + const strokeDashOffset = `-${arcOffset}`; return [strokeDasharray, strokeDashOffset]; } + protected renderArc( + id: string, + value: number | undefined, + inverted: boolean | undefined + ) { + const limit = inverted ? this.max : this.min; + + const path = svgArc({ + x: 0, + y: 0, + start: 0, + end: MAX_ANGLE, + r: RADIUS, + }); + + const current = this.current ?? limit; + const target = value ?? limit; + + const showActive = inverted ? target <= current : current <= target; + + const activeArcDashArray = showActive + ? inverted + ? this._strokeDashArc(target, current) + : this._strokeDashArc(current, target) + : this._strokeCircleDashArc(target); + + const arcDashArray = inverted + ? this._strokeDashArc(target, limit) + : this._strokeDashArc(limit, target); + + const targetCircleDashArray = this._strokeCircleDashArc(target); + + const currentCircleDashArray = + this.current != null && showActive + ? this._strokeCircleDashArc(this.current) + : undefined; + + return svg` + + + + + ${ + currentCircleDashArray + ? svg` + + ` + : nothing + } + `; + } + protected render(): TemplateResult { - const trackPath = arc({ x: 0, y: 0, start: 0, end: MAX_ANGLE, r: RADIUS }); + const trackPath = svgArc({ + x: 0, + y: 0, + start: 0, + end: MAX_ANGLE, + r: RADIUS, + }); const lowValue = this.dual ? this._localLow : this._localValue; const highValue = this._localHigh; - const lowPercentage = this._valueToPercentage(lowValue ?? this.min); - const highPercentage = this._valueToPercentage(highValue ?? this.max); + const current = this.current; - const [lowStrokeDasharray, lowStrokeDashOffset] = this._strokeDashArc( - lowPercentage, - this.inverted - ); - - const [highStrokeDasharray, highStrokeDashOffset] = this._strokeDashArc( - highPercentage, - true - ); - - const currentPercentage = this._valueToPercentage(this.current ?? 0); - const currentAngle = currentPercentage * MAX_ANGLE; + const currentStroke = current + ? this._strokeCircleDashArc(current) + : undefined; return html` - ${lowValue != null + ${currentStroke ? svg` - - ` + + ` + : nothing} + ${lowValue != null + ? this.renderArc( + this.dual ? "low" : "value", + lowValue, + this.inverted + ) : nothing} ${this.dual && highValue != null - ? svg` - - ` - : nothing} - ${this.current != null - ? svg` - - - - - ` + ? this.renderArc("high", highValue, true) : nothing} @@ -531,7 +562,7 @@ export class HaControlCircularSlider extends LitElement { return css` :host { --control-circular-slider-color: var(--primary-color); - --control-circular-slider-background: #8b97a3; + --control-circular-slider-background: var(--disabled-color); --control-circular-slider-background-opacity: 0.3; --control-circular-slider-low-color: var( --control-circular-slider-color @@ -573,8 +604,7 @@ export class HaControlCircularSlider extends LitElement { stroke-width: 24px; } - .track { - outline: none; + .arc { fill: none; stroke-linecap: round; stroke-width: 24px; @@ -586,29 +616,64 @@ export class HaControlCircularSlider extends LitElement { opacity 180ms ease-in-out; } - .track:focus-visible { - stroke-width: 28px; - } - - .pressed .track { - transition: stroke-width 300ms ease-in-out; + .target { + fill: none; + stroke-linecap: round; + stroke-width: 18px; + stroke: white; + transition: + stroke-width 300ms ease-in-out, + stroke-dasharray 300ms ease-in-out, + stroke-dashoffset 300ms ease-in-out, + stroke 180ms ease-in-out, + opacity 180ms ease-in-out; } .current { - stroke: var(--primary-text-color); - transform: rotate(var(--current-angle, 0)); - transition: transform 300ms ease-in-out; + fill: none; + stroke-linecap: round; + stroke-width: 8px; + stroke: white; + opacity: 0.6; + transition: + stroke-width 300ms ease-in-out, + stroke-dasharray 300ms ease-in-out, + stroke-dashoffset 300ms ease-in-out, + stroke 180ms ease-in-out, + opacity 180ms ease-in-out; } - #value { + .arc-clear { + stroke: var(--clear-background-color); + } + .arc-background { + opacity: 0.5; + } + .arc-active { + outline: none; + } + .arc-active:focus-visible { + stroke-width: 28px; + } + + .pressed .arc, + .pressed .target, + .pressed .current { + transition: + stroke-width 300ms ease-in-out, + stroke 180ms ease-in-out, + opacity 180ms ease-in-out; + } + + .value { stroke: var(--control-circular-slider-color); } - #low { + .low { stroke: var(--control-circular-slider-low-color); } - #high { + .high { stroke: var(--control-circular-slider-high-color); } `; diff --git a/src/resources/ha-style.ts b/src/resources/ha-style.ts index aefe6e6479..b8a039d3d6 100644 --- a/src/resources/ha-style.ts +++ b/src/resources/ha-style.ts @@ -45,7 +45,8 @@ documentContainer.innerHTML = ` --card-background-color: #ffffff; --primary-background-color: #fafafa; --secondary-background-color: #e5e5e5; /* behind the cards on state */ - + --clear-background-color: #ffffff; + /* for header */ --header-height: 56px; diff --git a/src/resources/styles.ts b/src/resources/styles.ts index 37b4f65577..1c077cb815 100644 --- a/src/resources/styles.ts +++ b/src/resources/styles.ts @@ -4,6 +4,7 @@ export const darkStyles = { "primary-background-color": "#111111", "card-background-color": "#1c1c1c", "secondary-background-color": "#282828", + "clear-background-color": "#111111", "primary-text-color": "#e1e1e1", "secondary-text-color": "#9b9b9b", "disabled-text-color": "#6f6f6f", diff --git a/src/resources/svg-arc.ts b/src/resources/svg-arc.ts index 67e29b5157..fabf19185e 100644 --- a/src/resources/svg-arc.ts +++ b/src/resources/svg-arc.ts @@ -25,7 +25,7 @@ type ArcOptions = { rotate?: number; }; -export const arc = (options: ArcOptions) => { +export const svgArc = (options: ArcOptions) => { const { x, y, r, start, end, rotate = 0 } = options; const cx = x; const cy = y;