mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-09 22:57:59 -05:00
app/chatview: migrate PrivMessage to text2 API
This commit is contained in:
@@ -105,6 +105,29 @@ impl TextContext {
|
|||||||
window_scale: f32,
|
window_scale: f32,
|
||||||
width: Option<f32>,
|
width: Option<f32>,
|
||||||
underlines: &[Range<usize>],
|
underlines: &[Range<usize>],
|
||||||
|
) -> parley::Layout<Color> {
|
||||||
|
self.make_layout2(
|
||||||
|
text,
|
||||||
|
text_color,
|
||||||
|
font_size,
|
||||||
|
lineheight,
|
||||||
|
window_scale,
|
||||||
|
width,
|
||||||
|
underlines,
|
||||||
|
&[],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn make_layout2(
|
||||||
|
&mut self,
|
||||||
|
text: &str,
|
||||||
|
text_color: Color,
|
||||||
|
font_size: f32,
|
||||||
|
lineheight: f32,
|
||||||
|
window_scale: f32,
|
||||||
|
width: Option<f32>,
|
||||||
|
underlines: &[Range<usize>],
|
||||||
|
foreground_colors: &[(Range<usize>, Color)],
|
||||||
) -> parley::Layout<Color> {
|
) -> parley::Layout<Color> {
|
||||||
let mut builder =
|
let mut builder =
|
||||||
self.layout_ctx.ranged_builder(&mut self.font_ctx, &text, window_scale, false);
|
self.layout_ctx.ranged_builder(&mut self.font_ctx, &text, window_scale, false);
|
||||||
@@ -120,6 +143,10 @@ impl TextContext {
|
|||||||
builder.push(parley::StyleProperty::Underline(true), underline.clone());
|
builder.push(parley::StyleProperty::Underline(true), underline.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (range, color) in foreground_colors {
|
||||||
|
builder.push(parley::StyleProperty::Brush(*color), range.clone());
|
||||||
|
}
|
||||||
|
|
||||||
let mut layout: parley::Layout<Color> = builder.build(&text);
|
let mut layout: parley::Layout<Color> = builder.build(&text);
|
||||||
layout.break_all_lines(width);
|
layout.break_all_lines(width);
|
||||||
layout.align(width, parley::Alignment::Start, parley::AlignmentOptions::default());
|
layout.align(width, parley::Alignment::Start, parley::AlignmentOptions::default());
|
||||||
|
|||||||
@@ -70,12 +70,7 @@ pub struct PrivMessage {
|
|||||||
|
|
||||||
is_selected: bool,
|
is_selected: bool,
|
||||||
|
|
||||||
time_glyphs: Vec<Glyph>,
|
mesh_cache: Option<Vec<DrawInstruction>>,
|
||||||
unwrapped_glyphs: Vec<Glyph>,
|
|
||||||
wrapped_lines: Vec<Vec<Glyph>>,
|
|
||||||
|
|
||||||
atlas: text::RenderedAtlas,
|
|
||||||
mesh_cache: Option<DrawMesh>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PrivMessage {
|
impl PrivMessage {
|
||||||
@@ -89,27 +84,17 @@ impl PrivMessage {
|
|||||||
nick: String,
|
nick: String,
|
||||||
text: String,
|
text: String,
|
||||||
|
|
||||||
line_width: f32,
|
_line_width: f32,
|
||||||
timestamp_width: f32,
|
_timestamp_width: f32,
|
||||||
|
|
||||||
text_shaper: &TextShaper,
|
_text_shaper: &TextShaper,
|
||||||
render_api: &RenderApi,
|
_render_api: &RenderApi,
|
||||||
) -> Message {
|
) -> Message {
|
||||||
let timestr = Self::gen_timestr(timestamp);
|
|
||||||
let time_glyphs = text_shaper.shape(timestr, timestamp_font_size, window_scale);
|
|
||||||
|
|
||||||
let linetext = if nick == "NOTICE" { text.clone() } else { format!("{nick} {text}") };
|
|
||||||
if nick == "NOTICE" {
|
if nick == "NOTICE" {
|
||||||
font_size *= 0.8;
|
font_size *= 0.8;
|
||||||
}
|
}
|
||||||
let unwrapped_glyphs = text_shaper.shape(linetext, font_size, window_scale);
|
|
||||||
|
|
||||||
let mut atlas = text::Atlas::new(render_api, gfxtag!("chatview_privmsg"));
|
Message::Priv(Self {
|
||||||
atlas.push(&time_glyphs);
|
|
||||||
atlas.push(&unwrapped_glyphs);
|
|
||||||
let atlas = atlas.make();
|
|
||||||
|
|
||||||
let mut self_ = Self {
|
|
||||||
font_size,
|
font_size,
|
||||||
timestamp_font_size,
|
timestamp_font_size,
|
||||||
window_scale,
|
window_scale,
|
||||||
@@ -119,14 +104,8 @@ impl PrivMessage {
|
|||||||
text,
|
text,
|
||||||
confirmed: true,
|
confirmed: true,
|
||||||
is_selected: false,
|
is_selected: false,
|
||||||
time_glyphs,
|
|
||||||
unwrapped_glyphs,
|
|
||||||
wrapped_lines: vec![],
|
|
||||||
atlas,
|
|
||||||
mesh_cache: None,
|
mesh_cache: None,
|
||||||
};
|
})
|
||||||
self_.adjust_width(line_width, timestamp_width);
|
|
||||||
Message::Priv(self_)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gen_timestr(timestamp: Timestamp) -> String {
|
fn gen_timestr(timestamp: Timestamp) -> String {
|
||||||
@@ -136,226 +115,125 @@ impl PrivMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn height(&self, line_height: f32) -> f32 {
|
fn height(&self, line_height: f32) -> f32 {
|
||||||
self.wrapped_lines.len() as f32 * line_height
|
// TODO: calculate based on actual layout height
|
||||||
|
// For now, assume a single line
|
||||||
|
line_height
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gen_mesh(
|
async fn gen_mesh(
|
||||||
&mut self,
|
&mut self,
|
||||||
clip: &Rectangle,
|
clip: &Rectangle,
|
||||||
line_height: f32,
|
line_height: f32,
|
||||||
msg_spacing: f32,
|
msg_spacing: f32,
|
||||||
baseline: f32,
|
_baseline: f32,
|
||||||
timestamp_width: f32,
|
timestamp_width: f32,
|
||||||
nick_colors: &[Color],
|
nick_colors: &[Color],
|
||||||
timestamp_color: Color,
|
timestamp_color: Color,
|
||||||
text_color: Color,
|
text_color: Color,
|
||||||
hi_bg_color: Color,
|
hi_bg_color: Color,
|
||||||
debug_render: bool,
|
_debug_render: bool,
|
||||||
render_api: &RenderApi,
|
render_api: &RenderApi,
|
||||||
) -> DrawMesh {
|
) -> Vec<DrawInstruction> {
|
||||||
if let Some(mesh) = &self.mesh_cache {
|
if let Some(instrs) = &self.mesh_cache {
|
||||||
return mesh.clone()
|
return instrs.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
//t!("gen_mesh({})", glyph_str(&self.unwrapped_glyphs));
|
let mut all_instrs = vec![DrawInstruction::Move(Point::new(0., -line_height))];
|
||||||
let mut mesh = MeshBuilder::new(gfxtag!("chatview_privmsg"));
|
|
||||||
|
|
||||||
|
// Draw selection background if selected
|
||||||
if self.is_selected {
|
if self.is_selected {
|
||||||
let height = self.height(line_height) + msg_spacing;
|
let height = self.height(line_height) + msg_spacing;
|
||||||
|
let mut mesh = MeshBuilder::new(gfxtag!("chatview_privmsg_sel"));
|
||||||
mesh.draw_filled_box(
|
mesh.draw_filled_box(
|
||||||
&Rectangle { x: 0., y: -height, w: clip.w, h: height },
|
&Rectangle { x: 0., y: -height, w: clip.w, h: height },
|
||||||
hi_bg_color,
|
hi_bg_color,
|
||||||
);
|
);
|
||||||
|
all_instrs
|
||||||
|
.push(DrawInstruction::Draw(mesh.alloc(render_api).draw_with_textures(vec![])));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.render_timestamp(&mut mesh, baseline, line_height, timestamp_color);
|
let mut txt_ctx = text2::TEXT_CTX.get().await;
|
||||||
let off_x = timestamp_width;
|
|
||||||
|
|
||||||
let nick_color = select_nick_color(&self.nick, nick_colors);
|
|
||||||
|
|
||||||
let last_idx = self.wrapped_lines.len() - 1;
|
|
||||||
for (i, line) in self.wrapped_lines.iter().rev().enumerate() {
|
|
||||||
let off_y = (i + 1) as f32 * line_height;
|
|
||||||
let is_last_line = i == last_idx;
|
|
||||||
|
|
||||||
// debug draw baseline
|
|
||||||
if debug_render {
|
|
||||||
let y = baseline - off_y;
|
|
||||||
mesh.draw_filled_box(&Rectangle { x: 0., y: y - 1., w: clip.w, h: 1. }, COLOR_BLUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.render_line(
|
|
||||||
&mut mesh,
|
|
||||||
line,
|
|
||||||
off_x,
|
|
||||||
off_y,
|
|
||||||
is_last_line,
|
|
||||||
baseline,
|
|
||||||
nick_color,
|
|
||||||
text_color,
|
|
||||||
debug_render,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if debug_render {
|
|
||||||
let height = self.height(line_height);
|
|
||||||
mesh.draw_outline(
|
|
||||||
&Rectangle { x: 0., y: -height, w: clip.w, h: height },
|
|
||||||
COLOR_PINK,
|
|
||||||
1.,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mesh = mesh.alloc(render_api);
|
|
||||||
let mesh = mesh.draw_with_textures(vec![self.atlas.texture.clone()]);
|
|
||||||
self.mesh_cache = Some(mesh.clone());
|
|
||||||
|
|
||||||
mesh
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_timestamp(
|
|
||||||
&self,
|
|
||||||
mesh: &mut MeshBuilder,
|
|
||||||
baseline: f32,
|
|
||||||
line_height: f32,
|
|
||||||
timestamp_color: Color,
|
|
||||||
) {
|
|
||||||
let off_y = self.wrapped_lines.len() as f32 * line_height;
|
|
||||||
|
|
||||||
let glyph_pos_iter = GlyphPositionIter::new(
|
|
||||||
self.timestamp_font_size,
|
|
||||||
self.window_scale,
|
|
||||||
&self.time_glyphs,
|
|
||||||
baseline,
|
|
||||||
);
|
|
||||||
for (mut glyph_rect, glyph) in glyph_pos_iter.zip(self.time_glyphs.iter()) {
|
|
||||||
let uv_rect = self.atlas.fetch_uv(glyph.glyph_id).expect("missing glyph UV rect");
|
|
||||||
glyph_rect.y -= off_y;
|
|
||||||
|
|
||||||
mesh.draw_box(&glyph_rect, timestamp_color, uv_rect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_line(
|
|
||||||
&self,
|
|
||||||
mesh: &mut MeshBuilder,
|
|
||||||
line: &Vec<Glyph>,
|
|
||||||
off_x: f32,
|
|
||||||
off_y: f32,
|
|
||||||
is_last: bool,
|
|
||||||
baseline: f32,
|
|
||||||
nick_color: Color,
|
|
||||||
mut text_color: Color,
|
|
||||||
_debug_render: bool,
|
|
||||||
) {
|
|
||||||
//debug!(target: "ui::chatview", "render_line({})", glyph_str(line));
|
|
||||||
// Keep track of the 'section'
|
|
||||||
// Section 0 is the nickname (colorized)
|
|
||||||
// Section >=1 is just the message itself
|
|
||||||
let mut section = 1;
|
|
||||||
if is_last && self.nick != "NOTICE" {
|
|
||||||
section = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.nick == "NOTICE" {
|
|
||||||
text_color[0] = 0.35;
|
|
||||||
text_color[1] = 0.81;
|
|
||||||
text_color[2] = 0.89;
|
|
||||||
}
|
|
||||||
|
|
||||||
let glyph_pos_iter =
|
|
||||||
GlyphPositionIter::new(self.font_size, self.window_scale, line, baseline);
|
|
||||||
let Some(last_rect) = glyph_pos_iter.last() else { return };
|
|
||||||
let rhs = last_rect.rhs();
|
|
||||||
if self.nick == "NOTICE" {
|
|
||||||
mesh.draw_box(
|
|
||||||
&Rectangle::new(off_x, -off_y, rhs, baseline),
|
|
||||||
[0., 0.14, 0.16, 1.],
|
|
||||||
&Rectangle::zero(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let glyph_pos_iter =
|
|
||||||
GlyphPositionIter::new(self.font_size, self.window_scale, line, baseline);
|
|
||||||
for (mut glyph_rect, glyph) in glyph_pos_iter.zip(line.iter()) {
|
|
||||||
let uv_rect = self.atlas.fetch_uv(glyph.glyph_id).expect("missing glyph UV rect");
|
|
||||||
|
|
||||||
glyph_rect.x += off_x;
|
|
||||||
glyph_rect.y -= off_y;
|
|
||||||
|
|
||||||
let mut color = match section {
|
|
||||||
0 => nick_color,
|
|
||||||
_ => {
|
|
||||||
if self.confirmed {
|
|
||||||
text_color
|
|
||||||
} else {
|
|
||||||
UNCONF_COLOR
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//if debug_render {
|
|
||||||
// mesh.draw_outline(&glyph_rect, COLOR_BLUE, 2.);
|
|
||||||
//}
|
|
||||||
|
|
||||||
if glyph.sprite.has_color {
|
|
||||||
color = COLOR_WHITE;
|
|
||||||
}
|
|
||||||
|
|
||||||
mesh.draw_box(&glyph_rect, color, uv_rect);
|
|
||||||
|
|
||||||
if is_last && section < 1 && is_whitespace(&glyph.substr) {
|
|
||||||
section += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// clear_mesh() must be called after this.
|
|
||||||
fn adjust_params(
|
|
||||||
&mut self,
|
|
||||||
mut font_size: f32,
|
|
||||||
timestamp_font_size: f32,
|
|
||||||
window_scale: f32,
|
|
||||||
line_width: f32,
|
|
||||||
timestamp_width: f32,
|
|
||||||
text_shaper: &TextShaper,
|
|
||||||
render_api: &RenderApi,
|
|
||||||
) {
|
|
||||||
if self.nick == "NOTICE" {
|
|
||||||
font_size *= 0.8;
|
|
||||||
}
|
|
||||||
self.font_size = font_size;
|
|
||||||
self.timestamp_font_size = timestamp_font_size;
|
|
||||||
self.window_scale = window_scale;
|
|
||||||
|
|
||||||
|
// Timestamp layout
|
||||||
let timestr = Self::gen_timestr(self.timestamp);
|
let timestr = Self::gen_timestr(self.timestamp);
|
||||||
self.time_glyphs = text_shaper.shape(timestr, timestamp_font_size, window_scale);
|
let timestamp_layout = txt_ctx.make_layout(
|
||||||
|
×tr,
|
||||||
|
timestamp_color,
|
||||||
|
self.timestamp_font_size,
|
||||||
|
line_height / self.timestamp_font_size,
|
||||||
|
self.window_scale,
|
||||||
|
None,
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Message text layout
|
||||||
let linetext = if self.nick == "NOTICE" {
|
let linetext = if self.nick == "NOTICE" {
|
||||||
self.text.clone()
|
self.text.clone()
|
||||||
} else {
|
} else {
|
||||||
format!("{} {}", self.nick, self.text)
|
format!("{} {}", self.nick, self.text)
|
||||||
};
|
};
|
||||||
self.unwrapped_glyphs = text_shaper.shape(linetext, font_size, window_scale);
|
|
||||||
|
|
||||||
let mut atlas = text::Atlas::new(render_api, gfxtag!("chatview_privmsg"));
|
let nick_color = select_nick_color(&self.nick, nick_colors);
|
||||||
atlas.push(&self.time_glyphs);
|
|
||||||
atlas.push(&self.unwrapped_glyphs);
|
|
||||||
self.atlas = atlas.make();
|
|
||||||
|
|
||||||
// We need to rewrap the glyphs since they've been reloaded
|
let text_layout = if self.nick == "NOTICE" {
|
||||||
self.adjust_width(line_width, timestamp_width);
|
txt_ctx.make_layout(
|
||||||
|
&linetext,
|
||||||
|
text_color,
|
||||||
|
self.font_size,
|
||||||
|
line_height / self.font_size,
|
||||||
|
self.window_scale,
|
||||||
|
Some(clip.w - timestamp_width),
|
||||||
|
&[],
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let body_color = if self.confirmed { text_color } else { UNCONF_COLOR };
|
||||||
|
let nick_end = self.nick.len() + 1;
|
||||||
|
txt_ctx.make_layout2(
|
||||||
|
&linetext,
|
||||||
|
body_color,
|
||||||
|
self.font_size,
|
||||||
|
line_height / self.font_size,
|
||||||
|
self.window_scale,
|
||||||
|
Some(clip.w - timestamp_width),
|
||||||
|
&[],
|
||||||
|
&[(0..nick_end, nick_color)],
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Render timestamp
|
||||||
|
let timestamp_instrs =
|
||||||
|
text2::render_layout(×tamp_layout, render_api, gfxtag!("chatview_privmsg_ts"));
|
||||||
|
all_instrs.extend(timestamp_instrs);
|
||||||
|
|
||||||
|
// Render message text offset by timestamp_width
|
||||||
|
all_instrs.push(DrawInstruction::Move(Point::new(timestamp_width, 0.)));
|
||||||
|
let text_instrs =
|
||||||
|
text2::render_layout(&text_layout, render_api, gfxtag!("chatview_privmsg_text"));
|
||||||
|
all_instrs.extend(text_instrs);
|
||||||
|
|
||||||
|
self.mesh_cache = Some(all_instrs.clone());
|
||||||
|
all_instrs
|
||||||
}
|
}
|
||||||
|
|
||||||
/// clear_mesh() must be called after this.
|
fn adjust_params(
|
||||||
fn adjust_width(&mut self, line_width: f32, timestamp_width: f32) {
|
&mut self,
|
||||||
let width = line_width - timestamp_width;
|
font_size: f32,
|
||||||
// clamp to > 0
|
timestamp_font_size: f32,
|
||||||
let width = max(width, 0.);
|
window_scale: f32,
|
||||||
|
_line_width: f32,
|
||||||
|
_timestamp_width: f32,
|
||||||
|
_text_shaper: &TextShaper,
|
||||||
|
_render_api: &RenderApi,
|
||||||
|
) {
|
||||||
|
let font_size = if self.nick == "NOTICE" { font_size * 0.8 } else { font_size };
|
||||||
|
self.font_size = font_size;
|
||||||
|
self.timestamp_font_size = timestamp_font_size;
|
||||||
|
self.window_scale = window_scale;
|
||||||
|
}
|
||||||
|
|
||||||
// Invalidate wrapped_glyphs and recalc
|
fn adjust_width(&mut self, _line_width: f32, _timestamp_width: f32) {
|
||||||
self.wrapped_lines =
|
// Width changes affect wrapping, so clear the mesh cache
|
||||||
text::wrap(width, self.font_size, self.window_scale, &self.unwrapped_glyphs);
|
self.mesh_cache = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_mesh(&mut self) {
|
fn clear_mesh(&mut self) {
|
||||||
@@ -850,7 +728,7 @@ impl Message {
|
|||||||
) -> Vec<DrawInstruction> {
|
) -> Vec<DrawInstruction> {
|
||||||
match self {
|
match self {
|
||||||
Self::Priv(m) => {
|
Self::Priv(m) => {
|
||||||
let mesh = m.gen_mesh(
|
m.gen_mesh(
|
||||||
clip,
|
clip,
|
||||||
line_height,
|
line_height,
|
||||||
msg_spacing,
|
msg_spacing,
|
||||||
@@ -862,8 +740,8 @@ impl Message {
|
|||||||
hi_bg_color,
|
hi_bg_color,
|
||||||
debug_render,
|
debug_render,
|
||||||
render_api,
|
render_api,
|
||||||
);
|
)
|
||||||
vec![DrawInstruction::Draw(mesh)]
|
.await
|
||||||
}
|
}
|
||||||
Self::Date(m) => {
|
Self::Date(m) => {
|
||||||
m.gen_mesh(
|
m.gen_mesh(
|
||||||
|
|||||||
Reference in New Issue
Block a user