improved sliders, textinput and layout explanations

This commit is contained in:
Jakob Pinterits
2024-06-07 16:54:40 +02:00
parent 2550a37d01
commit 89bfe69dcd
5 changed files with 82 additions and 22 deletions

View File

@@ -54,19 +54,25 @@ export class SliderComponent extends ComponentBase {
return this.state.value;
}
// Calculate the value as a fraction of the track width
// Calculate the selected value from the event coordinates
let rect = this.innerElement.getBoundingClientRect();
let fraction = (event.clientX - rect.left) / rect.width;
fraction = Math.max(0, Math.min(1, fraction));
// Enforce the step size
let valueRange = this.state.maximum - this.state.minimum;
let value = fraction * valueRange + this.state.minimum;
// Enforce the step size
//
// Converting to a value, and back to fractions may seem convoluted, but
// this ensures that the step size is enforced correctly. Careless math
// can lead to floating point errors that cause the reported value to be
// off by a little (e.g. 7.99999999 instead of 8).
if (this.state.step !== 0) {
let normalizedStepSize = this.state.step / valueRange;
let stepIndex = Math.round(value / this.state.step);
value = stepIndex * this.state.step;
fraction =
Math.round(fraction / normalizedStepSize) * normalizedStepSize;
fraction = (value - this.state.minimum) / valueRange;
}
// Move the knob
@@ -76,7 +82,7 @@ export class SliderComponent extends ComponentBase {
);
// Return the new value
return fraction * valueRange + this.state.minimum;
return value;
}
private onDragStart(event: MouseEvent): boolean {

View File

@@ -82,11 +82,15 @@ export class TextInputComponent extends ComponentBase {
// date value.
this.inputElement.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
// Update the state
this.state.text = this.inputElement.value;
this.onChangeLimiter.call(this.state.text);
this.onChangeLimiter.flush();
// There is no need for the debouncer to report this call, since
// Python will already trigger both change & confirm events when
// it receives the message that is about to be sent.
this.onChangeLimiter.clear();
// Inform the backend
this.sendMessageToBackend({
text: this.state.text,
});

View File

@@ -119,4 +119,15 @@ export class Debouncer {
this.mostRecentPerformedCall = Date.now();
this.pendingArguments = null;
}
/// Clears any pending calls, ensuring that the debouncer will not call the
/// function in the future unless `call` is invoked again.
public clear(): void {
this.pendingArguments = null;
if (this.timeout !== null) {
clearTimeout(this.timeout);
this.timeout = null;
}
}
}

View File

@@ -187,11 +187,13 @@ class TextInput(KeyboardFocusableFundamentalComponent):
self._apply_delta_state_from_frontend({"text": msg["text"]})
# Trigger both the change event...
await self.call_event_handler(
self.on_change,
TextInputChangeEvent(self.text),
)
# And the confirm event
await self.call_event_handler(
self.on_confirm,
TextInputConfirmEvent(self.text),

View File

@@ -17,7 +17,33 @@ import rio.data_models
# - revealer
# - scroll_container
# - scroll_target
# - slideshow
# These components pass on the entirety of the available space to their
# children
FULL_SIZE_SINGLE_CONTAINERS: set[type[rio.Component]] = {
rio.Button,
rio.Card,
rio.Container,
rio.CustomListItem,
rio.KeyEventListener,
rio.Link,
rio.MouseEventListener,
rio.PageView,
rio.Rectangle,
rio.Slideshow,
rio.Stack,
rio.Switcher,
}
# These components make use of the `"grow"` value with `width`, `height` or
# both.
CONTAINERS_SUPPORTING_GROW: Iterable[type[rio.Component]] = {
rio.Column,
rio.Grid,
rio.Row,
}
class LayoutExplainer:
@@ -124,9 +150,11 @@ class LayoutExplainer:
if axis == "width":
parent_allocated_space = self._layout.parent_allocated_width
parent_natural_size = self._layout.parent_natural_width
specified_size = self.component.width
else:
parent_allocated_space = self._layout.parent_allocated_height
parent_natural_size = self._layout.parent_natural_height
specified_size = self.component.height
parent_class_name = type(parent).__name__
@@ -135,19 +163,7 @@ class LayoutExplainer:
return f"This is the app's top-level component. As such, the {target_class_name} was allocated the full a {axis} of {allocated_space_before_alignment:.1f} available in the window."
# Single container?
if type(parent) in (
rio.Button,
rio.Card,
rio.Container,
rio.CustomListItem,
rio.KeyEventListener,
rio.MouseEventListener,
rio.Link,
rio.PageView,
rio.Rectangle,
rio.Stack,
rio.Switcher,
):
if type(parent) in FULL_SIZE_SINGLE_CONTAINERS:
return f"Because `{parent_class_name}` components pass on all available space to their children, the component's {axis} is the full {allocated_space_before_alignment:.1f} units available in its parent."
# Overlay
@@ -163,6 +179,11 @@ class LayoutExplainer:
or isinstance(parent, rio.Column)
and axis == "width"
):
if specified_size == "grow":
self.warnings.append(
f'The component has `{axis}="grow"` set, but it is placed inside of a `{parent_class_name}`. Because {parent_class_name}s pass on the entire available space in this direction to all children it has no effect.'
)
return f"The component is placed inside of a {parent_class_name}. Since all children of {parent_class_name}s receive the full {axis}, it has received the entire {allocated_space_before_alignment:.1f} units available in its parent."
# Major axis
@@ -236,6 +257,11 @@ class LayoutExplainer:
allocated the space it was, in a single direction.
"""
# Prepare some values based on axis
try:
parent = self.session._weak_components_by_id[self._layout.parent_id]
except KeyError:
parent = None
if axis == "width":
allocated_space = self._layout.allocated_width
allocated_space_before_alignment = (
@@ -269,6 +295,17 @@ class LayoutExplainer:
target_class_name = type(self.component).__name__
# Warn if the component has a `grow` attribute set, but is placed inside
# of a container that doesn't support it
if (
specified_size == "grow"
and parent is not None
and type(parent) not in CONTAINERS_SUPPORTING_GROW
):
self.warnings.append(
f'The component has `{axis}="grow"` set, but it is placed inside of a `{type(parent).__name__}`. {type(parent).__name__} components can not make use of this property, so it has no effect.'
)
# How much space did the parent hand down?
result = io.StringIO()
result.write(