gpui-component/docs/docs/components/slider.md

458 lines
12 KiB
Markdown

---
title: Slider
description: A control that allows the user to select values from a range using a draggable thumb.
---
# Slider
A slider component for selecting numeric values within a specified range. Supports both single value and range selection modes, horizontal and vertical orientations, custom styling, and step intervals.
## Import
```rust
use gpui_component::slider::{Slider, SliderState, SliderEvent, SliderValue};
```
## Usage
### Basic Slider
```rust
let slider_state = cx.new(|_| {
SliderState::new()
.min(0.0)
.max(100.0)
.default_value(50.0)
.step(1.0)
});
Slider::new(&slider_state)
```
### Slider with Event Handling
```rust
struct MyView {
slider_state: Entity<SliderState>,
current_value: f32,
}
impl MyView {
fn new(cx: &mut Context<Self>) -> Self {
let slider_state = cx.new(|_| {
SliderState::new()
.min(0.0)
.max(100.0)
.default_value(25.0)
.step(5.0)
});
let subscription = cx.subscribe(&slider_state, |this, _, event: &SliderEvent, cx| {
match event {
SliderEvent::Change(value) => {
this.current_value = value.start();
cx.notify();
}
}
});
Self {
slider_state,
current_value: 25.0,
}
}
}
impl Render for MyView {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex()
.gap_2()
.child(Slider::new(&self.slider_state))
.child(format!("Value: {}", self.current_value))
}
}
```
### Range Slider
```rust
let range_slider = cx.new(|_| {
SliderState::new()
.min(0.0)
.max(100.0)
.default_value(20.0..80.0) // Range from 20 to 80
.step(1.0)
});
Slider::new(&range_slider)
```
### Vertical Slider
```rust
Slider::new(&slider_state)
.vertical()
.h(px(200.))
```
### Custom Step Intervals
```rust
// Integer steps
let integer_slider = cx.new(|_| {
SliderState::new()
.min(0.0)
.max(10.0)
.step(1.0)
.default_value(5.0)
});
// Decimal steps
let decimal_slider = cx.new(|_| {
SliderState::new()
.min(0.0)
.max(1.0)
.step(0.01)
.default_value(0.5)
});
```
### Min/Max Configuration
```rust
// Temperature slider
let temp_slider = cx.new(|_| {
SliderState::new()
.min(-10.0)
.max(40.0)
.default_value(20.0)
.step(0.5)
});
// Percentage slider
let percent_slider = cx.new(|_| {
SliderState::new()
.min(0.0)
.max(100.0)
.default_value(75.0)
.step(5.0)
});
```
### Disabled State
```rust
Slider::new(&slider_state)
.disabled(true)
```
### Custom Styling
```rust
Slider::new(&slider_state)
.bg(cx.theme().success)
.text_color(cx.theme().success_foreground)
.rounded(px(4.))
```
## API Reference
### SliderState
| Method | Description |
| --------------------------------------- | --------------------------------------------- |
| `new()` | Create a new slider state with default values |
| `min(f32)` | Set minimum value (default: 0.0) |
| `max(f32)` | Set maximum value (default: 100.0) |
| `step(f32)` | Set step interval (default: 1.0) |
| `default_value(impl Into<SliderValue>)` | Set initial value |
| `set_value(impl Into<SliderValue>)` | Update slider value |
| `value()` | Get current slider value |
### Slider
| Method | Description |
| --------------------------- | ------------------------------------ |
| `new(&Entity<SliderState>)` | Create a new slider bound to state |
| `horizontal()` | Set horizontal orientation (default) |
| `vertical()` | Set vertical orientation |
| `disabled(bool)` | Set disabled state |
### SliderValue
The slider supports two types of values:
| Variant | Description |
| ----------------- | --------------------------------- |
| `Single(f32)` | A single numeric value |
| `Range(f32, f32)` | A range with start and end values |
#### Methods
| Method | Description |
| ----------------- | ----------------------------------------------- |
| `start()` | Get the start value (for both single and range) |
| `end()` | Get the end value (for both single and range) |
| `is_single()` | Check if value is single |
| `is_range()` | Check if value is range |
| `clamp(min, max)` | Clamp value to range |
#### Conversions
```rust
// From f32
let single_value: SliderValue = 42.0.into();
// From tuple
let range_value: SliderValue = (10.0, 90.0).into();
// From Range
let range_value: SliderValue = (10.0..90.0).into();
```
### SliderEvent
| Event | Description |
| --------------------- | --------------------------------- |
| `Change(SliderValue)` | Emitted when slider value changes |
### Styling
The slider component implements `Styled` trait and supports:
- Background color for track and thumb
- Text color for thumb
- Border radius
- Size customization
## Examples
### Color Picker
```rust
struct ColorPicker {
hue_slider: Entity<SliderState>,
saturation_slider: Entity<SliderState>,
lightness_slider: Entity<SliderState>,
alpha_slider: Entity<SliderState>,
current_color: Hsla,
}
impl ColorPicker {
fn new(cx: &mut Context<Self>) -> Self {
let hue_slider = cx.new(|_| {
SliderState::new()
.min(0.0)
.max(1.0)
.step(0.01)
.default_value(0.5)
});
let saturation_slider = cx.new(|_| {
SliderState::new()
.min(0.0)
.max(1.0)
.step(0.01)
.default_value(1.0)
});
// Subscribe to all sliders to update color
let subscriptions = [&hue_slider, &saturation_slider /* ... */]
.iter()
.map(|slider| {
cx.subscribe(slider, |this, _, event: &SliderEvent, cx| {
match event {
SliderEvent::Change(_) => {
this.update_color(cx);
}
}
})
})
.collect::<Vec<_>>();
Self {
hue_slider,
saturation_slider,
// ... other fields
}
}
fn update_color(&mut self, cx: &mut Context<Self>) {
let h = self.hue_slider.read(cx).value().start();
let s = self.saturation_slider.read(cx).value().start();
// ... calculate color
self.current_color = hsla(h, s, l, a);
cx.notify();
}
}
impl Render for ColorPicker {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
h_flex()
.gap_4()
.child(
v_flex()
.gap_2()
.child("Hue")
.child(Slider::new(&self.hue_slider).vertical().h(px(120.)))
)
.child(
v_flex()
.gap_2()
.child("Saturation")
.child(Slider::new(&self.saturation_slider).vertical().h(px(120.)))
)
// ... other sliders
}
}
```
### Volume Control
```rust
struct VolumeControl {
volume_slider: Entity<SliderState>,
volume: f32,
}
impl VolumeControl {
fn new(cx: &mut Context<Self>) -> Self {
let volume_slider = cx.new(|_| {
SliderState::new()
.min(0.0)
.max(100.0)
.step(1.0)
.default_value(50.0)
});
let subscription = cx.subscribe(&volume_slider, |this, _, event: &SliderEvent, cx| {
match event {
SliderEvent::Change(value) => {
this.volume = value.start();
this.apply_volume_change();
cx.notify();
}
}
});
Self {
volume_slider,
volume: 50.0,
}
}
fn apply_volume_change(&self) {
// Apply volume change to audio system
println!("Volume changed to: {}%", self.volume);
}
}
impl Render for VolumeControl {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
h_flex()
.items_center()
.gap_3()
.child("🔊")
.child(Slider::new(&self.volume_slider).flex_1())
.child(format!("{}%", self.volume as i32))
}
}
```
### Price Range Filter
```rust
struct PriceFilter {
price_range: Entity<SliderState>,
min_price: f32,
max_price: f32,
}
impl PriceFilter {
fn new(cx: &mut Context<Self>) -> Self {
let price_range = cx.new(|_| {
SliderState::new()
.min(0.0)
.max(1000.0)
.step(10.0)
.default_value(100.0..500.0) // Range slider
});
let subscription = cx.subscribe(&price_range, |this, _, event: &SliderEvent, cx| {
match event {
SliderEvent::Change(value) => {
this.min_price = value.start();
this.max_price = value.end();
this.filter_products();
cx.notify();
}
}
});
Self {
price_range,
min_price: 100.0,
max_price: 500.0,
}
}
fn filter_products(&self) {
println!("Filtering products: ${} - ${}", self.min_price, self.max_price);
}
}
impl Render for PriceFilter {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
v_flex()
.gap_2()
.child("Price Range")
.child(Slider::new(&self.price_range))
.child(format!("${} - ${}", self.min_price as i32, self.max_price as i32))
}
}
```
### Temperature Slider with Custom Styling
```rust
struct TemperatureControl {
temp_slider: Entity<SliderState>,
temperature: f32,
}
impl Render for TemperatureControl {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let temp_color = if self.temperature < 10.0 {
cx.theme().info // Cold - blue
} else if self.temperature > 25.0 {
cx.theme().destructive // Hot - red
} else {
cx.theme().success // Comfortable - green
};
v_flex()
.gap_3()
.child("Temperature Control")
.child(
Slider::new(&self.temp_slider)
.bg(temp_color)
.text_color(cx.theme().background)
.rounded(px(8.))
)
.child(format!("{}°C", self.temperature as i32))
}
}
```
## Keyboard Shortcuts
| Key | Action |
| ------------- | ------------------------------ |
| `←` / `↓` | Decrease value by step |
| `→` / `↑` | Increase value by step |
| `Page Down` | Decrease by larger amount |
| `Page Up` | Increase by larger amount |
| `Home` | Set to minimum value |
| `End` | Set to maximum value |
| `Tab` | Move focus to next element |
| `Shift + Tab` | Move focus to previous element |