@exegia/specular 0.2.1 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +131 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,6 +18,137 @@ Pointer-following rim highlights and inset bezel utilities for Tailwind v4 + Rea
|
|
|
18
18
|
|
|
19
19
|
---
|
|
20
20
|
|
|
21
|
+
## Native Rust / GPUI
|
|
22
|
+
|
|
23
|
+
[`gpui-specular`](https://crates.io/crates/gpui-specular) provides native inset lighting and pointer-following bezels. It uses GPUI directly; no Tailwind, React or web view is needed.
|
|
24
|
+
|
|
25
|
+
### 1. Install the Rust dependencies
|
|
26
|
+
|
|
27
|
+
In your app's `Cargo.toml`:
|
|
28
|
+
|
|
29
|
+
```toml
|
|
30
|
+
[dependencies]
|
|
31
|
+
gpui = { package = "gpui-pre", version = "=0.3.8", default-features = false }
|
|
32
|
+
gpui_platform = { package = "gpui-pre-platform", version = "=0.3.8", default-features = false, features = ["font-kit"] }
|
|
33
|
+
gpui-specular = "0.1.0"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Use the same GPUI package/version throughout your app: types from `gpui`, a Git checkout, and `gpui-pre` are not interchangeable. This setup targets macOS. On Linux, enable `x11` or `wayland` on `gpui_platform` and install GPUI's native system dependencies. Linux and Windows have not been validated here.
|
|
37
|
+
|
|
38
|
+
### 2. Add a static bezel
|
|
39
|
+
|
|
40
|
+
```rust
|
|
41
|
+
use gpui::{div, px, rgb, Styled};
|
|
42
|
+
use gpui_specular::{SpecularExt, Surface};
|
|
43
|
+
|
|
44
|
+
let tile = div()
|
|
45
|
+
.size(px(160.0))
|
|
46
|
+
.rounded(px(32.0))
|
|
47
|
+
.bg(rgb(0x404040))
|
|
48
|
+
.specular(Surface::dark());
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Use `Surface::light()` for light fills. `.specular(...)` replaces the element's shadow list; your app owns its fill, radius and theme. Custom `Layer` alpha is **0–1**, not the TypeScript config's 0–100 percentages. See the [crate guide](crates/gpui-specular/README.md) for layer configuration.
|
|
52
|
+
|
|
53
|
+
### 3. Make the light follow the mouse
|
|
54
|
+
|
|
55
|
+
Store the angle in your view. Handle mouse movement on the surrounding container, calculate the bearing to the **surface center**, then call `cx.notify()` to render the updated angle. The complete `src/main.rs` below centers the tile in the window; move the mouse to aim the light, and click the tile to switch themes.
|
|
56
|
+
|
|
57
|
+
```rust
|
|
58
|
+
use gpui::{
|
|
59
|
+
App, Bounds, Context, MouseMoveEvent, Window, WindowBounds, WindowOptions, div, point,
|
|
60
|
+
prelude::*, px, rgb, size,
|
|
61
|
+
};
|
|
62
|
+
use gpui_specular::{SpecularExt, Surface, pointer_angle};
|
|
63
|
+
|
|
64
|
+
struct Demo {
|
|
65
|
+
angle: f32,
|
|
66
|
+
dark: bool,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
impl Render for Demo {
|
|
70
|
+
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
|
71
|
+
let surface = if self.dark {
|
|
72
|
+
Surface::dark()
|
|
73
|
+
} else {
|
|
74
|
+
Surface::light()
|
|
75
|
+
};
|
|
76
|
+
div()
|
|
77
|
+
.id("bezel-demo")
|
|
78
|
+
.size_full()
|
|
79
|
+
.flex()
|
|
80
|
+
.items_center()
|
|
81
|
+
.justify_center()
|
|
82
|
+
.bg(rgb(if self.dark { 0x171717 } else { 0xf5f5f4 }))
|
|
83
|
+
.on_mouse_move(cx.listener(|this, event: &MouseMoveEvent, window, cx| {
|
|
84
|
+
let viewport = window.viewport_size();
|
|
85
|
+
let center = point(viewport.width / 2.0, viewport.height / 2.0);
|
|
86
|
+
if let Some(angle) = pointer_angle(event.position, center) {
|
|
87
|
+
this.angle = angle;
|
|
88
|
+
cx.notify();
|
|
89
|
+
}
|
|
90
|
+
}))
|
|
91
|
+
.child(
|
|
92
|
+
div()
|
|
93
|
+
.id("surface")
|
|
94
|
+
.size(px(180.0))
|
|
95
|
+
.rounded(px(36.0))
|
|
96
|
+
.flex()
|
|
97
|
+
.items_center()
|
|
98
|
+
.justify_center()
|
|
99
|
+
.bg(rgb(if self.dark { 0x404040 } else { 0xd6d3d1 }))
|
|
100
|
+
.text_color(rgb(if self.dark { 0xe5e5e5 } else { 0x404040 }))
|
|
101
|
+
.specular(surface.rotated(self.angle))
|
|
102
|
+
.on_click(cx.listener(|this, _, _, cx| {
|
|
103
|
+
this.dark = !this.dark;
|
|
104
|
+
cx.notify();
|
|
105
|
+
}))
|
|
106
|
+
.child("Click to switch theme"),
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
fn main() {
|
|
112
|
+
gpui_platform::application().run(|cx: &mut App| {
|
|
113
|
+
cx.on_window_closed(|cx, _| {
|
|
114
|
+
if cx.windows().is_empty() {
|
|
115
|
+
cx.quit();
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
.detach();
|
|
119
|
+
let bounds = Bounds::centered(None, size(px(520.0), px(420.0)), cx);
|
|
120
|
+
cx.open_window(
|
|
121
|
+
WindowOptions {
|
|
122
|
+
window_bounds: Some(WindowBounds::Windowed(bounds)),
|
|
123
|
+
..Default::default()
|
|
124
|
+
},
|
|
125
|
+
|_, cx| {
|
|
126
|
+
cx.new(|_| Demo {
|
|
127
|
+
angle: 0.0,
|
|
128
|
+
dark: true,
|
|
129
|
+
})
|
|
130
|
+
},
|
|
131
|
+
)
|
|
132
|
+
.expect("open the Specular demo window");
|
|
133
|
+
cx.activate(true);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Run your app with `cargo run`. To try the checked-in example from this repository:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
cargo run -p gpui-specular --example bezel
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Coordinate rule:** `event.position` and the surface center must both be in window coordinates. `window.viewport_size() / 2` works here only because the tile is centered in a full-window container. For tiles elsewhere, use their actual layout bounds; do not reuse the window center. Attaching the handler to the surrounding container lets tracking continue outside the tile.
|
|
145
|
+
|
|
146
|
+
`pointer_angle` returns 0° above the center and 90° to its right. At the exact center it returns `None`, so the example preserves the previous angle. Always call `.rotated(angle)` on the original surface, not on the previous frame's rotated surface.
|
|
147
|
+
|
|
148
|
+
**Optional smoothing:** the example tracks immediately. For gradual motion, store a target angle on mouse movement and call `damp_angle(current, target, amount)` on animation frames. Use `amount = 1.0 - (-dt_seconds / 0.12).exp()` for a 120 ms response, call `cx.notify()`, and request the next frame only until settled. Updating only on mouse events will stop easing as soon as the pointer stops. Your app owns frame scheduling and reduced-motion preferences; keep the surface static when motion is disabled.
|
|
149
|
+
|
|
150
|
+
This crate currently supplies inset lighting and direction helpers. Image-overlay rims and image-tone sampling remain web-only.
|
|
151
|
+
|
|
21
152
|
## Install
|
|
22
153
|
|
|
23
154
|
```bash
|