Skip to content

Commit e87304d

Browse files
committed
feat(ie-render): add browser chrome overlay rendering #78
Chrome overlay system (chrome.rs): - ChromeOverlay struct with optional address bar, tab list, bookmarks - build_chrome_display_list generates PaintCommands for overlays - Address bar: dark semi-transparent bar at top with URL text and cursor indicator - Tab list: left-side panel with tab titles/URLs, active tab highlighted - Bookmarks: right-side panel with title/URL entries - Styled with consistent dark theme colors Shell integration (app.rs): - build_chrome_overlay() maps OverlayState to ChromeOverlay data - paint() appends chrome commands after page content - request_redraw() after every action and address bar keypress - Overlays appear on Ctrl+L/Ctrl+Shift+T/Ctrl+Shift+B, dismiss on Escape
1 parent 6ba6bfe commit e87304d

3 files changed

Lines changed: 343 additions & 1 deletion

File tree

crates/ie-render/src/chrome.rs

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
use crate::paint::{Color, PaintCommand};
2+
3+
/// Browser chrome overlay data for rendering.
4+
pub struct ChromeOverlay {
5+
pub address_bar: Option<AddressBarOverlay>,
6+
pub tab_list: Option<TabListOverlay>,
7+
pub bookmarks: Option<BookmarkListOverlay>,
8+
}
9+
10+
pub struct AddressBarOverlay {
11+
pub text: String,
12+
pub cursor: usize,
13+
}
14+
15+
pub struct TabListOverlay {
16+
pub tabs: Vec<TabEntry>,
17+
pub active_index: usize,
18+
}
19+
20+
pub struct TabEntry {
21+
pub id: u64,
22+
pub title: String,
23+
pub url: String,
24+
}
25+
26+
pub struct BookmarkListOverlay {
27+
pub bookmarks: Vec<BookmarkEntry>,
28+
}
29+
30+
pub struct BookmarkEntry {
31+
pub title: String,
32+
pub url: String,
33+
}
34+
35+
impl ChromeOverlay {
36+
pub fn none() -> Self {
37+
Self {
38+
address_bar: None,
39+
tab_list: None,
40+
bookmarks: None,
41+
}
42+
}
43+
44+
pub fn is_active(&self) -> bool {
45+
self.address_bar.is_some() || self.tab_list.is_some() || self.bookmarks.is_some()
46+
}
47+
}
48+
49+
/// Build paint commands for chrome overlays (rendered on top of page content).
50+
pub fn build_chrome_display_list(
51+
chrome: &ChromeOverlay,
52+
viewport_width: f32,
53+
_viewport_height: f32,
54+
) -> Vec<PaintCommand> {
55+
let mut commands = Vec::new();
56+
57+
if let Some(bar) = &chrome.address_bar {
58+
render_address_bar(&mut commands, bar, viewport_width);
59+
}
60+
61+
if let Some(tabs) = &chrome.tab_list {
62+
render_tab_list(&mut commands, tabs, viewport_width);
63+
}
64+
65+
if let Some(bookmarks) = &chrome.bookmarks {
66+
render_bookmark_list(&mut commands, bookmarks, viewport_width);
67+
}
68+
69+
commands
70+
}
71+
72+
const BAR_HEIGHT: f32 = 40.0;
73+
const BAR_BG: Color = Color {
74+
r: 30,
75+
g: 30,
76+
b: 50,
77+
a: 230,
78+
};
79+
const BAR_TEXT: Color = Color {
80+
r: 255,
81+
g: 255,
82+
b: 255,
83+
a: 255,
84+
};
85+
const BAR_CURSOR: Color = Color {
86+
r: 100,
87+
g: 180,
88+
b: 255,
89+
a: 255,
90+
};
91+
const PANEL_BG: Color = Color {
92+
r: 40,
93+
g: 40,
94+
b: 60,
95+
a: 240,
96+
};
97+
const ACTIVE_BG: Color = Color {
98+
r: 60,
99+
g: 80,
100+
b: 120,
101+
a: 240,
102+
};
103+
const ITEM_TEXT: Color = Color {
104+
r: 220,
105+
g: 220,
106+
b: 230,
107+
a: 255,
108+
};
109+
const URL_TEXT: Color = Color {
110+
r: 140,
111+
g: 160,
112+
b: 200,
113+
a: 255,
114+
};
115+
116+
fn render_address_bar(commands: &mut Vec<PaintCommand>, bar: &AddressBarOverlay, width: f32) {
117+
// Background bar across top
118+
commands.push(PaintCommand::FillRect {
119+
x: 0.0,
120+
y: 0.0,
121+
width,
122+
height: BAR_HEIGHT,
123+
color: BAR_BG,
124+
});
125+
126+
// URL text
127+
let text_x = 12.0;
128+
let text_y = 10.0;
129+
let font_size = 16.0;
130+
if !bar.text.is_empty() {
131+
commands.push(PaintCommand::Text {
132+
text: bar.text.clone(),
133+
x: text_x,
134+
y: text_y,
135+
font_size,
136+
color: BAR_TEXT,
137+
});
138+
}
139+
140+
// Cursor
141+
let cursor_x = text_x + bar.cursor as f32 * font_size * 0.5;
142+
commands.push(PaintCommand::FillRect {
143+
x: cursor_x,
144+
y: text_y,
145+
width: 2.0,
146+
height: font_size,
147+
color: BAR_CURSOR,
148+
});
149+
}
150+
151+
fn render_tab_list(commands: &mut Vec<PaintCommand>, tabs: &TabListOverlay, width: f32) {
152+
let panel_width = 300.0f32.min(width * 0.4);
153+
let item_height = 36.0;
154+
let panel_height = (tabs.tabs.len() as f32 * item_height).max(item_height);
155+
156+
// Panel background
157+
commands.push(PaintCommand::FillRect {
158+
x: 0.0,
159+
y: 0.0,
160+
width: panel_width,
161+
height: panel_height,
162+
color: PANEL_BG,
163+
});
164+
165+
for (i, tab) in tabs.tabs.iter().enumerate() {
166+
let y = i as f32 * item_height;
167+
168+
// Active tab highlight
169+
if i == tabs.active_index {
170+
commands.push(PaintCommand::FillRect {
171+
x: 0.0,
172+
y,
173+
width: panel_width,
174+
height: item_height,
175+
color: ACTIVE_BG,
176+
});
177+
}
178+
179+
// Tab title
180+
let title = if tab.title.is_empty() {
181+
"New Tab"
182+
} else {
183+
&tab.title
184+
};
185+
commands.push(PaintCommand::Text {
186+
text: title.to_string(),
187+
x: 12.0,
188+
y: y + 6.0,
189+
font_size: 14.0,
190+
color: ITEM_TEXT,
191+
});
192+
193+
// Tab URL (smaller, below title)
194+
if !tab.url.is_empty() {
195+
let url_display = if tab.url.len() > 35 {
196+
format!("{}...", &tab.url[..35])
197+
} else {
198+
tab.url.clone()
199+
};
200+
commands.push(PaintCommand::Text {
201+
text: url_display,
202+
x: 12.0,
203+
y: y + 22.0,
204+
font_size: 10.0,
205+
color: URL_TEXT,
206+
});
207+
}
208+
}
209+
}
210+
211+
fn render_bookmark_list(
212+
commands: &mut Vec<PaintCommand>,
213+
bookmarks: &BookmarkListOverlay,
214+
width: f32,
215+
) {
216+
let panel_width = 350.0f32.min(width * 0.5);
217+
let item_height = 32.0;
218+
let panel_height = (bookmarks.bookmarks.len() as f32 * item_height)
219+
.max(item_height)
220+
.min(400.0);
221+
222+
// Panel background
223+
commands.push(PaintCommand::FillRect {
224+
x: width - panel_width,
225+
y: 0.0,
226+
width: panel_width,
227+
height: panel_height,
228+
color: PANEL_BG,
229+
});
230+
231+
for (i, bm) in bookmarks.bookmarks.iter().enumerate() {
232+
let y = i as f32 * item_height;
233+
if y + item_height > panel_height {
234+
break;
235+
}
236+
237+
let x_offset = width - panel_width + 12.0;
238+
239+
// Title
240+
commands.push(PaintCommand::Text {
241+
text: bm.title.clone(),
242+
x: x_offset,
243+
y: y + 6.0,
244+
font_size: 13.0,
245+
color: ITEM_TEXT,
246+
});
247+
248+
// URL
249+
let url_display = if bm.url.len() > 40 {
250+
format!("{}...", &bm.url[..40])
251+
} else {
252+
bm.url.clone()
253+
};
254+
commands.push(PaintCommand::Text {
255+
text: url_display,
256+
x: x_offset,
257+
y: y + 20.0,
258+
font_size: 10.0,
259+
color: URL_TEXT,
260+
});
261+
}
262+
}

crates/ie-render/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,16 @@
33
//! Rendering engine for Internet Exploder.
44
//! GPU-accelerated via wgpu, with software fallback for tests.
55
6+
pub mod chrome;
67
pub mod gpu;
78
pub mod paint;
89
pub mod software;
910
pub mod text;
1011

12+
pub use chrome::{
13+
AddressBarOverlay, BookmarkEntry, BookmarkListOverlay, ChromeOverlay, TabEntry, TabListOverlay,
14+
build_chrome_display_list,
15+
};
1116
pub use gpu::GpuRenderer;
1217
pub use paint::{Color, PaintCommand, build_display_list};
1318
pub use software::{SoftwareTextMeasure, render_to_buffer};

crates/ie-shell/src/app.rs

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,10 @@ impl Browser {
171171
event_loop.exit();
172172
}
173173
}
174+
// Redraw after any action to update chrome overlays
175+
if let Some(window) = &self.window {
176+
window.request_redraw();
177+
}
174178
}
175179

176180
fn handle_address_bar_input(&mut self, event: &winit::event::KeyEvent) {
@@ -291,14 +295,82 @@ impl Browser {
291295
}
292296

293297
fn paint(&mut self) {
294-
let commands = self.render_page();
298+
let mut commands = self.render_page();
299+
300+
// Append chrome overlay commands on top of page content
301+
let (vw, vh) = self
302+
.gpu_renderer
303+
.as_ref()
304+
.map(|r| r.size())
305+
.unwrap_or((800, 600));
306+
let chrome = self.build_chrome_overlay();
307+
commands.extend(ie_render::build_chrome_display_list(
308+
&chrome, vw as f32, vh as f32,
309+
));
310+
295311
if let Some(renderer) = &mut self.gpu_renderer
296312
&& let Err(e) = renderer.render(&commands)
297313
{
298314
tracing::error!("render error: {e}");
299315
}
300316
}
301317

318+
fn build_chrome_overlay(&self) -> ie_render::ChromeOverlay {
319+
match &self.overlay {
320+
OverlayState::None => ie_render::ChromeOverlay::none(),
321+
OverlayState::AddressBar(bar) => ie_render::ChromeOverlay {
322+
address_bar: Some(ie_render::AddressBarOverlay {
323+
text: bar.input.clone(),
324+
cursor: bar.cursor,
325+
}),
326+
tab_list: None,
327+
bookmarks: None,
328+
},
329+
OverlayState::TabList => {
330+
let tabs = self
331+
.tab_manager
332+
.tabs()
333+
.iter()
334+
.map(|t| ie_render::TabEntry {
335+
id: t.id.0,
336+
title: t.title.clone(),
337+
url: t
338+
.url
339+
.as_ref()
340+
.map(|u| u.as_str().to_string())
341+
.unwrap_or_default(),
342+
})
343+
.collect();
344+
let active_index = self
345+
.tab_manager
346+
.active_tab()
347+
.and_then(|at| self.tab_manager.tabs().iter().position(|t| t.id == at.id))
348+
.unwrap_or(0);
349+
ie_render::ChromeOverlay {
350+
address_bar: None,
351+
tab_list: Some(ie_render::TabListOverlay { tabs, active_index }),
352+
bookmarks: None,
353+
}
354+
}
355+
OverlayState::Bookmarks => {
356+
let bookmarks = self
357+
.bookmark_store
358+
.list()
359+
.iter()
360+
.map(|b| ie_render::BookmarkEntry {
361+
title: b.title.clone(),
362+
url: b.url.clone(),
363+
})
364+
.collect();
365+
ie_render::ChromeOverlay {
366+
address_bar: None,
367+
tab_list: None,
368+
bookmarks: Some(ie_render::BookmarkListOverlay { bookmarks }),
369+
}
370+
}
371+
}
372+
}
373+
302374
fn render_page(&self) -> Vec<ie_render::PaintCommand> {
303375
let source = self
304376
.tab_manager
@@ -447,6 +519,9 @@ impl ApplicationHandler<UserEvent> for Browser {
447519
return;
448520
}
449521
self.handle_address_bar_input(key_event);
522+
if let Some(window) = &self.window {
523+
window.request_redraw();
524+
}
450525
} else {
451526
// TabList / Bookmarks: Escape dismisses, other keys go through
452527
if let Some(action) =

0 commit comments

Comments
 (0)