-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ui):
LoadingButton
component (#37)
* feat: basic `LoadingButton` * feat: add custom animate * feat: export component in index, clean up code
- Loading branch information
Showing
3 changed files
with
166 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
/** | ||
a loading animation intended for replacing build-in component Spinner | ||
it repeat a same animate forever. | ||
the arc in animation controlled by head and tail, assert(head>=tail). | ||
head and tail follow a same easing, but not in same time by using different offset. | ||
the `easing` function will compress given easing function according to offset. | ||
given easing function require `float(float)` function signature, input 0~1, output 0~1 | ||
*/ | ||
import { Token } from "../global.slint"; | ||
component LoadingAnimation inherits Path { | ||
// available option | ||
// the time of a repetition | ||
in property <duration> circle-time: 1000ms; | ||
// the min distance between head and tail (percentage of a circle) | ||
in property <float> distance: 0.1; | ||
// width | ||
stroke-width: 3px; | ||
// color | ||
stroke: Token.color.on-primary-container; | ||
// implement detail | ||
viewbox-height: 1; | ||
viewbox-width: 1; | ||
private property <float> radius: 0.5; | ||
// 0~1, means the progress of a repetition | ||
private property <float> progress: Math.mod(animation-tick() / 1ms,circle-time / 1ms) / (circle-time / 1ms); | ||
private property <float> tail: easing(progress, distance) - distance / 2; | ||
private property <float> head: easing(progress, - distance) + distance / 2; | ||
private property <{x: float, y: float}> tail-point: polar-to-cartesian(radius, tail * 1turn); | ||
private property <{x: float, y: float}> head-point: polar-to-cartesian(radius, head * 1turn); | ||
MoveTo { | ||
x: tail-point.x; | ||
y: tail-point.y; | ||
} | ||
|
||
ArcTo { | ||
sweep: true; | ||
large-arc: head - tail > 0.5; | ||
radius-x: radius; | ||
radius-y: radius; | ||
x: head-point.x; | ||
y: head-point.y; | ||
} | ||
|
||
pure function easing(x: float, offset: float) -> float { | ||
if (offset > 0) { | ||
return x > offset ? _inner-easing((x - offset) / (1 - offset)) : 0; | ||
} else if (offset < 0) { | ||
return x < 1 + offset ? _inner-easing(x / (1 + offset)) : 1; | ||
} else { | ||
return _inner-easing(x); | ||
} | ||
} | ||
// convenient easing change | ||
pure function _inner-easing(x: float) -> float { | ||
return ease-in-out-quint(x); | ||
} | ||
// easing function from https://easings.net/ | ||
pure function ease-in-out-sine(x: float) -> float { | ||
return -(Math.cos(3.1415926 * x * 1rad) - 1) / 2; | ||
} | ||
pure function ease-in-out-quint(x: float) -> float { | ||
return x < 0.5 ? 16 * x * x * x * x * x : 1 - Math.pow(-2 * x + 2, 5) / 2; | ||
} | ||
// polar to cartesian coordinate system | ||
pure function polar-to-cartesian(r: float, theta: angle) -> {x: float, y: float} { | ||
return { | ||
x: 0.5 + r * Math.cos(theta - 90deg), | ||
y: 0.5 + r * Math.sin(theta - 90deg), | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import { StateLayer } from "state_layer.slint"; | ||
import { Token } from "../global.slint"; | ||
import { LoadingAnimation } from "animation.slint"; | ||
|
||
export component LoadingButton inherits Rectangle { | ||
// available option | ||
in property <color> on-surface: Token.color.on-surface; | ||
in property <color> surface: Token.color.surface-container; | ||
in property <bool> is-loading: false; | ||
in property <string> content: "No Content"; | ||
in property <length> font-size: Token.font.body.large.size; | ||
height: 40px; | ||
callback clicked <=> area.clicked; | ||
// implement detail | ||
private property <length> max-radius: 20px; | ||
private property <length> icon-size: 18px; | ||
private property <length> default-padding: 24px; | ||
private property <length> icon-padding: 16px; | ||
private property <length> default-spacing: 8px; | ||
clip: true; | ||
background: surface; | ||
border-radius: self.height / 2 > max-radius ? max-radius : self.height / 2; | ||
StateLayer { | ||
background: on-surface; | ||
is-hover: area.has-hover; | ||
is-press: area.pressed; | ||
} | ||
|
||
_spinner := LoadingAnimation { | ||
x: icon-padding; | ||
width: icon-size; | ||
stroke: on-surface; | ||
} | ||
|
||
_text := Text { | ||
x: (root.width - self.width) / 2; | ||
text: content; | ||
font-size: font-size; | ||
vertical-alignment: center; | ||
horizontal-alignment: center; | ||
} | ||
|
||
area := TouchArea { | ||
mouse-cursor: pointer; | ||
} | ||
|
||
states [ | ||
// width = icon-padding + icon-size + default-spacing + _text.width + default-padding | ||
loading when is-loading: { | ||
width: icon-padding + icon-size + default-spacing + _text.width + default-padding; | ||
_text.x: icon-padding + icon-size + default-spacing; | ||
_spinner.opacity: 1; | ||
in { | ||
animate width, _text.x, _spinner.opacity { | ||
duration: 200ms; | ||
easing: ease-in-out-quint; | ||
} | ||
} | ||
out { | ||
animate width, _text.x, _spinner.opacity { | ||
duration: 200ms; | ||
easing: ease-in-out-quint; | ||
} | ||
} | ||
} | ||
// width = default-padding + _text.width + default-padding | ||
normal when !is-loading: { | ||
width: default-padding + _text.width + default-padding; | ||
_text.x: default-padding; | ||
_spinner.opacity: 0; | ||
} | ||
] | ||
} | ||
|
||
// quick preview | ||
component Example { | ||
LoadingButton { | ||
content: "click me"; | ||
clicked => { | ||
self.is-loading = !self.is-loading; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters