Skip to content

Conversation

@moWerk
Copy link
Member

@moWerk moWerk commented Apr 28, 2025

Add New QML Components for the QuickSettings Redesign

This pull request introduces four new QML components to the qml-asteroid module, enhancing the AsteroidOS QuickSettings rework. These components are designed to provide polished, reusable UI elements for our smartwatch interface, integrated into the import asteroid.controls 1.0 context. Below is a brief specification and example use case for each component.

1. OptionsCycler

Specification:
OptionsCycler is a QML component that allows users to cycle through a predefined array of configuration values for a specific key in a configuration object. It displays a title and the current value, updating the configuration when the user taps to cycle through options. The component supports dynamic value arrays and integrates with Nemo.Configuration for persistent settings. It features smooth opacity transitions (250ms) for visual feedback and is modelled after the LabeledSwitch for good touch interaction on small screens.
optionscycler

Example Use Case:
In the QuickSettings configuration page, OptionsCycler is used to select the particle animation design (e.g., "diamonds", "bubbles", "logos", "flashes") for the battery meter. Users tap to cycle through designs, with the selected value saved to the options configuration for the ValueMeter component.

2. RemorseTimer

Specification:
RemorseTimer is a QML component that implements a countdown timer with a visual gauge for confirming or canceling actions. It supports customizable intervals (default 3000ms), gauge segments (default 6), and angular range (default: start: -130°, end: +265° from start). The component displays an action text and a cancel prompt (both translatable via qsTrId), triggering a signal when the timer completes unless canceled by user tap. It is ideal for actions requiring confirmation, such as system shutdowns.
remorse

Example Use Case:
In QuickSettings, RemorseTimer is used for the powerOffToggle and rebootToggle. When the user activates the power-off toggle, a 3-second countdown with a gauge appears, showing "Powering off in" and "Tap to cancel". If not canceled, it triggers a login1DBus call copied from the PowerPage.qml to power off the device.

3. Particles

Specification:
Particles is a QML component for rendering animated particles in meter or gauge visualizations. It supports multiple extendible designs (currently: "diamonds", "bubbles", "logos", "flashes") with customizable properties like maxSize, targetX, lifetime (default 1200ms), and clipBounds. Particles animate their position, size, and opacity using ParallelAnimation with InOutSine and OutQuad/InQuad easing curves. The component self-destructs after its lifetime or if it moves outside the clip bounds, optimizing performance.
particles

Example Use Case:
In the ValueMeter component, Particles enhances the battery meter animation. When the battery is charging, particles (e.g., diamond-shaped) spawn and move across the meter, scaling, fading and changing speed and direction to visualize the charging state. The design is configurable via the particleDesign option, set through OptionsCycler.

4. ValueMeter

Specification:
ValueMeter is a QML component for displaying a dynamic meter that visualizes values like battery percentage, volume, or brightness. It supports a range (valueLowerBound to valueUpperBound), animated fill transitions (250ms, InOutQuad easing), and optional colored fills based on value thresholds. The component integrates Particles for animations (enabled via enableAnimations) and supports opacity transitions (250ms) and color animations (300ms). It includes timers for resetting display modes (resetTimer, fadeOutTimer, fadeInTimer) and emits a resetDirection signal for toggle synchronization.
valuemeter

Example Use Case:
In QuickSettings, ValueMeter displays the battery percentage by default, switching to brightness or volume when the respective toggles (brightnessToggle, soundToggle) are adjusted. For example, during volume adjustment, it shows a blue-tinted meter (#4C9800A6) reflecting the current volume level, with smooth fill animations and a 2-second reset to battery display.


Changes

  • Added OptionsCycler.qml, RemorseTimer.qml, Particles.qml, and ValueMeter.qml to qml-asteroid.
  • Integrated components into asteroid.controls 1.0 for use in QuickSettings.qml and QuickSettingsPage.qml.
  • Ensured Qt 2.9 compatibility with timed animations and touch-friendly UX.
  • Implemented small, iterative changes with no cross/side effects, validated via intense testing.

Commit Notes


This PR lays the foundation for an enhanced QuickSettings experience in AsteroidOS, making the interface more interactive and visually engaging.
Please review and provide feedback!

moWerk added 5 commits May 9, 2025 14:21
… value cycling

- Introduced OptionCycler, a reusable QML component in org.asteroid.controls, analogous to LabeledSwitch, for cycling through configuration values system-wide.
- Implemented title label (top-left-aligned) and value label (centered) for clear presentation of configuration options.
- Enabled value cycling through a provided array on click, updating a specified configObject key and emitting a valueChanged signal.
- Added conditional touch disabling via HighlightBar onClicked handler, preserving ListView scrolling and supporting external state checks (e.g., batteryAnimation).
- Supported visual feedback with configurable opacity (e.g., 0.5 when disabled) for intuitive user interaction.
- Provided comprehensive QML documentation with an example, following LabeledSwitch style.
- Configured customizable layout with labelMargin, labelFontSize, and flexible height (typically rowHeight * 2 in use).
…eter animations

- Introduced BatteryParticles, a reusable QML component in org.asteroid.controls, for animated particles in battery meter visualizations.
- Supported multiple designs (diamonds, bubbles, logos, flashes) with dynamic size and opacity properties per design.
- Implemented position, scale, and opacity animations, with self-destruction after a configurable lifetime.
- Added QML documentation and example, styled like LabeledSwitch and OptionCycler, for system-wide use.
- Registered component in org.asteroid.controls qmldir for module-based access.
- Updated designProperties for subtler particle sizes:
  - Diamonds/Bubbles: initialSize 0.3 (was 0.4), maxSize 0.9 (was 1.0).
  - Logos: initialSize 0.4 (was 0.5), maxSize 1.2.
  - Flashes: initialSize 0.6, maxSize 1.4 (unchanged).
- Set maxOpacity to 0.6 across all designs for consistent appearance.
- Removed console logs for PR readiness.
…tdowns

Introduce a new 'RemorseTimer' QML component in 'org.asteroid.controls', designed for system-wide use in confirming critical actions like power-off or reboot. The component displays a semi-transparent overlay with a customizable 'SegmentedArc' gauge, an action message, a countdown label, and a 'Tap to cancel' prompt. Key features include:

- Configurable countdown duration via the 'interval' property (default 3000ms).
- Customizable 'SegmentedArc' gauge with 'gaugeSegmentAmount' (default 6), 'gaugeStartDegree' (default -130), and 'gaugeEndFromStartDegree' (default 265) for flexible arc styling.
- Smooth arc progression using a 0-100 'inputValue' range, with segments depleting gradually over the interval.
- Precise second-by-second countdown display (e.g., 3->1 for 3000ms) synchronized with the arc.
- 300ms fade-in animation on start and fade-out on cancellation for a polished UX.
- Bold countdown label ('Dims.l(18)', 'SemiBoldCondensed') and action/cancel labels ('Dims.l(6)') with tight margins ('Dims.l(1)') for a modern look.
- Translated action and cancel messages using 'qsTrId' for internationalization.
- Signals for 'triggered' (on timeout) and 'cancelled' (on tap) to integrate with system actions.
- Introduced ValueMeter.qml, a reusable component in org.asteroid.controls for displaying values within a range, with properties for width, height, value bounds, current value, isIncreasing, animations, and particle design.
- Generalized battery meter logic, supporting wave animation and particle effects via Particles.qml.
- Added fillColor property to ValueMeter.qml, moving battery-specific color logic to callers.
- Removed distracting color pulse animation from ValueMeter.qml for cleaner visuals.
- Renamed BatteryParticles.qml to Particles.qml, generalizing nomenclature and changing isCharging to isIncreasing for consistency.
- Updated ValueMeter.qml to reference Particles.qml, ensuring compatibility.
@moWerk moWerk force-pushed the add-components-for-quicksettings branch from e701fef to e4a7239 Compare May 9, 2025 12:22
Copy link
Member

@MagneFire MagneFire left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't finished reviewing this yet. But thought I'd start here 😄


HighlightBar {
onClicked: {
if (configObject && configObject.batteryAnimation) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things:

  • Can you invert this if statement such that nesting is reduced.
  • what's batteryAnimation this PR references it only once, the documentation also doesn't mention it. Can it be removed?

HighlightBar {
onClicked: {
if (configObject && configObject.batteryAnimation) {
var currentIndex = valueArray.indexOf(currentValue)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would happen if the same text occurs in the array? Would things break?

Is this intended behavior or could this be solved by keeping track of the current index globally?

var nextIndex = (currentIndex + 1) % valueArray.length
var newValue = valueArray[nextIndex]
currentValue = newValue
if (configObject && configKey) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

configObject is already checked, why the additional check?

valueArray: designOptions
currentValue: config.design
onValueChanged: {
config.design = value
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this update config.design twice? Once via the internal logic using configObject and configKey?

If this is the case, then imo the better approach would be to just use the onValueChanged version.

\qmlproperty string RemorseTimer::cancelText
The text displayed for the cancel prompt (e.g., "Tap to cancel"). Can be a translated string.
*/
property string cancelText: "Tap to cancel"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is untranslated.

We don't seem to allow translations in qml-asteroid. So maybe it would work if this is changed to an alias:

    property alias text: cancelLabel.text


/*!
\qmlproperty string RemorseTimer::action
The action message displayed (e.g., "Powering Off"). Should be a translated string.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the distinction between Should be a translated string. and Can be a translated string. between the action and cancelText?

Either way, is it relevant that both should be translated? This is a detail for whoever decides to use it right?

\qmlproperty string RemorseTimer::action
The action message displayed (e.g., "Powering Off"). Should be a translated string.
*/
property string action: ""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can also be an alias right? That way you don't need to explicitly set it here. And it makes it probably consistent with the rest of the codebase.


// Start the timer and show the overlay
function start() {
countdownSeconds = Math.floor(interval / 1000); // Reset to 3
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the user doesn't use the interval = 3. Does it still reset to 3? 😉


Timer {
id: syncTimer
interval: remorseTimer.interval / Math.floor(remorseTimer.interval / 1000) // 1000ms for 3000ms
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment below, don't use your example value for something the user is probably going to override.

Since these types of comments are obvious, you might as well refrain from using them.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally this always returns 1000. Might as well use that right?

Example

remorseTimer.interval = 3000
interval: 3000 / floor(3000 / 1000) -> 3000 / floor(3) -> 1000

remorseTimer.interval = 5000
interval: 5000 / floor(5000 / 1000) -> 5000 / floor(5) -> 1000

Additionally this introduces a bug when remorseTimer.interval is less than 1000 as Math.floor(remorseTimer.interval / 1000) would yield zero. And division by zero results in errors.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants