1use std::{fmt, sync::Arc};
6
7use gpui_kit::{Context, Hsla, component::Theme};
8
9use super::{CellChange, CellStyle, LiveGridDelegate, LiveGridState};
10use crate::{
11 highlight::{ChangeDirection, HighlightPolicy, RowChange, RowStamps, Stamp},
12 model::ColumnIx,
13};
14
15const CHANGE_OPACITY: f32 = 0.45;
18
19#[derive(Clone, Copy, Debug)]
22pub struct ChangePalette {
23 inserted: Hsla,
24 updated: Hsla,
25 up: Hsla,
26 down: Hsla,
27 removed: Hsla,
28 removed_foreground: Hsla,
29}
30
31impl ChangePalette {
32 pub(super) fn new(theme: &Theme) -> Self {
34 Self {
35 inserted: theme.success,
36 updated: theme.warning,
37 up: theme.success,
38 down: theme.danger,
39 removed: theme.danger,
40 removed_foreground: theme.muted_foreground,
41 }
42 }
43
44 pub fn inserted(&self) -> Hsla {
46 self.inserted
47 }
48
49 pub fn updated(&self) -> Hsla {
51 self.updated
52 }
53
54 pub fn up(&self) -> Hsla {
56 self.up
57 }
58
59 pub fn down(&self) -> Hsla {
61 self.down
62 }
63
64 pub fn removed(&self) -> Hsla {
66 self.removed
67 }
68
69 pub fn removed_foreground(&self) -> Hsla {
72 self.removed_foreground
73 }
74}
75
76pub trait ChangePainter: 'static {
100 fn paint(&self, change: &CellChange, palette: &ChangePalette, style: &mut CellStyle);
102}
103
104fn change_background(change: &CellChange, color: Hsla) -> Hsla {
106 color.opacity(CHANGE_OPACITY * change.intensity())
107}
108
109#[inline]
112pub(super) fn stamp_change(
113 stamp: Stamp,
114 column: Option<ColumnIx>,
115 policy: &HighlightPolicy,
116 tick: u32,
117) -> Option<CellChange> {
118 policy.intensity(stamp, tick).map(|intensity| CellChange {
119 intensity,
120 change: stamp.change(),
121 direction: stamp.direction(),
122 column_ix: column,
123 })
124}
125
126#[derive(Clone, Copy, Debug)]
128pub(super) enum RowChanges<'a> {
129 Stamps(Option<RowStamps<'a>>),
131 Removed(CellChange),
133}
134
135impl RowChanges<'_> {
136 #[inline]
139 pub(super) fn cell(
140 self,
141 column: ColumnIx,
142 policy: &HighlightPolicy,
143 tick: u32,
144 ) -> Option<CellChange> {
145 match self {
146 Self::Stamps(stamps) => stamps
147 .and_then(|stamps| stamp_change(stamps.get(column), Some(column), policy, tick)),
148 Self::Removed(change) => Some(CellChange {
149 column_ix: Some(column),
150 ..change
151 }),
152 }
153 }
154}
155
156#[derive(Clone, Copy, Debug, Default)]
162pub struct UniformChanges;
163
164impl ChangePainter for UniformChanges {
165 fn paint(&self, change: &CellChange, palette: &ChangePalette, style: &mut CellStyle) {
166 match change.change() {
167 RowChange::Inserted => {
168 style.set_background(change_background(change, palette.inserted()));
169 }
170 RowChange::Updated => {
171 style.set_background(change_background(change, palette.updated()));
172 }
173 RowChange::Removed => {
174 style.set_background(change_background(change, palette.removed()));
175 style.set_foreground(palette.removed_foreground());
176 }
177 }
178 }
179}
180
181#[derive(Clone, Copy, Debug, Default)]
186pub struct DirectionalChanges;
187
188impl ChangePainter for DirectionalChanges {
189 fn paint(&self, change: &CellChange, palette: &ChangePalette, style: &mut CellStyle) {
190 match (change.change(), change.direction()) {
191 (RowChange::Updated, Some(direction)) => {
192 let color = match direction {
193 ChangeDirection::Up => palette.up(),
194 ChangeDirection::Down => palette.down(),
195 };
196 style.set_background(change_background(change, color));
197 style.set_trend(Some(direction));
198 }
199 _ => UniformChanges.paint(change, palette, style),
200 }
201 }
202}
203
204#[derive(Clone)]
208#[non_exhaustive]
209pub enum ChangePaint {
210 Uniform(UniformChanges),
212 Directional(DirectionalChanges),
214 Custom(Arc<dyn ChangePainter>),
216}
217
218impl Default for ChangePaint {
219 fn default() -> Self {
220 Self::Uniform(UniformChanges)
221 }
222}
223
224impl fmt::Debug for ChangePaint {
225 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226 match self {
227 Self::Uniform(painter) => f.debug_tuple("Uniform").field(painter).finish(),
228 Self::Directional(painter) => f.debug_tuple("Directional").field(painter).finish(),
229 Self::Custom(_) => f.write_str("Custom(..)"),
230 }
231 }
232}
233
234impl ChangePainter for ChangePaint {
235 #[inline]
236 fn paint(&self, change: &CellChange, palette: &ChangePalette, style: &mut CellStyle) {
237 match self {
238 Self::Uniform(painter) => painter.paint(change, palette, style),
239 Self::Directional(painter) => painter.paint(change, palette, style),
240 Self::Custom(painter) => painter.paint(change, palette, style),
241 }
242 }
243}
244
245impl<D: LiveGridDelegate> LiveGridState<D> {
246 pub fn with_change_paint(mut self, paint: ChangePaint) -> Self {
249 self.change_paint = paint;
250 self
251 }
252
253 pub fn set_change_paint(&mut self, paint: ChangePaint, cx: &mut Context<Self>) {
256 self.change_paint = paint;
257 cx.notify();
258 }
259
260 pub fn change_paint(&self) -> &ChangePaint {
262 &self.change_paint
263 }
264}