Merge pull request #1167 from zed-industries/block-scroll-width

Introduce a new `BlockStyle` field for blocks
This commit is contained in:
Antonio Scandurra 2022-06-10 14:03:46 +02:00 committed by GitHub
commit 799b32c8a9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 239 additions and 170 deletions

View file

@ -4,7 +4,7 @@ use anyhow::Result;
use collections::{BTreeMap, HashSet};
use editor::{
diagnostic_block_renderer,
display_map::{BlockDisposition, BlockId, BlockProperties, RenderBlock},
display_map::{BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock},
highlight_diagnostic_message, Autoscroll, Editor, ExcerptId, ExcerptRange, MultiBuffer,
ToOffset,
};
@ -348,6 +348,7 @@ impl ProjectDiagnosticsEditor {
blocks_to_add.push(BlockProperties {
position: header_position,
height: 2,
style: BlockStyle::Sticky,
render: diagnostic_header_renderer(primary),
disposition: BlockDisposition::Above,
});
@ -366,6 +367,7 @@ impl ProjectDiagnosticsEditor {
blocks_to_add.push(BlockProperties {
position: (excerpt_id.clone(), entry.range.start.clone()),
height: diagnostic.message.matches('\n').count() as u8 + 1,
style: BlockStyle::Fixed,
render: diagnostic_block_renderer(diagnostic, true),
disposition: BlockDisposition::Below,
});
@ -402,6 +404,7 @@ impl ProjectDiagnosticsEditor {
BlockProperties {
position: excerpts_snapshot.anchor_in_excerpt(excerpt_id, text_anchor),
height: block.height,
style: block.style,
render: block.render,
disposition: block.disposition,
}
@ -621,7 +624,6 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
.with_color(theme.warning_diagnostic.message.text.color)
};
let x_padding = cx.gutter_padding + cx.scroll_x * cx.em_width;
Flex::row()
.with_child(
icon.constrained()
@ -651,8 +653,8 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
}))
.contained()
.with_style(style.container)
.with_padding_left(x_padding)
.with_padding_right(x_padding)
.with_padding_left(cx.gutter_padding)
.with_padding_right(cx.gutter_padding)
.expanded()
.named("diagnostic header")
})

View file

@ -20,7 +20,7 @@ use wrap_map::WrapMap;
pub use block_map::{
BlockBufferRows as DisplayBufferRows, BlockChunks as DisplayChunks, BlockContext,
BlockDisposition, BlockId, BlockProperties, RenderBlock, TransformBlock,
BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock, TransformBlock,
};
pub trait ToDisplayPoint {
@ -650,6 +650,7 @@ pub mod tests {
height
);
BlockProperties {
style: BlockStyle::Fixed,
position,
height,
disposition,

View file

@ -56,6 +56,7 @@ pub struct Block {
id: BlockId,
position: Anchor,
height: u8,
style: BlockStyle,
render: Mutex<RenderBlock>,
disposition: BlockDisposition,
}
@ -67,10 +68,18 @@ where
{
pub position: P,
pub height: u8,
pub style: BlockStyle,
pub render: Arc<dyn Fn(&mut BlockContext) -> ElementBox>,
pub disposition: BlockDisposition,
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum BlockStyle {
Fixed,
Flex,
Sticky,
}
pub struct BlockContext<'a, 'b> {
pub cx: &'b mut RenderContext<'a, crate::Editor>,
pub anchor_x: f32,
@ -513,6 +522,7 @@ impl<'a> BlockMapWriter<'a> {
height: block.height,
render: Mutex::new(block.render),
disposition: block.disposition,
style: block.style,
}),
);
@ -940,6 +950,10 @@ impl Block {
pub fn position(&self) -> &Anchor {
&self.position
}
pub fn style(&self) -> BlockStyle {
self.style
}
}
impl Debug for Block {
@ -1018,18 +1032,21 @@ mod tests {
let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
let block_ids = writer.insert(vec![
BlockProperties {
style: BlockStyle::Fixed,
position: buffer_snapshot.anchor_after(Point::new(1, 0)),
height: 1,
disposition: BlockDisposition::Above,
render: Arc::new(|_| Empty::new().named("block 1")),
},
BlockProperties {
style: BlockStyle::Fixed,
position: buffer_snapshot.anchor_after(Point::new(1, 2)),
height: 2,
disposition: BlockDisposition::Above,
render: Arc::new(|_| Empty::new().named("block 2")),
},
BlockProperties {
style: BlockStyle::Fixed,
position: buffer_snapshot.anchor_after(Point::new(3, 3)),
height: 3,
disposition: BlockDisposition::Below,
@ -1183,12 +1200,14 @@ mod tests {
let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
writer.insert(vec![
BlockProperties {
style: BlockStyle::Fixed,
position: buffer_snapshot.anchor_after(Point::new(1, 12)),
disposition: BlockDisposition::Above,
render: Arc::new(|_| Empty::new().named("block 1")),
height: 1,
},
BlockProperties {
style: BlockStyle::Fixed,
position: buffer_snapshot.anchor_after(Point::new(1, 1)),
disposition: BlockDisposition::Below,
render: Arc::new(|_| Empty::new().named("block 2")),
@ -1286,6 +1305,7 @@ mod tests {
height
);
BlockProperties {
style: BlockStyle::Fixed,
position,
height,
disposition,

View file

@ -4801,6 +4801,7 @@ impl Editor {
cx.focus(&rename_editor);
let block_id = this.insert_blocks(
[BlockProperties {
style: BlockStyle::Flex,
position: range.start.clone(),
height: 1,
render: Arc::new({
@ -4985,6 +4986,7 @@ impl Editor {
let diagnostic = entry.diagnostic.clone();
let message_height = diagnostic.message.lines().count() as u8;
BlockProperties {
style: BlockStyle::Fixed,
position: buffer.anchor_after(entry.range.start),
height: message_height,
render: diagnostic_block_renderer(diagnostic, true),
@ -7932,6 +7934,7 @@ mod tests {
editor.update(cx, |editor, cx| {
editor.insert_blocks(
[BlockProperties {
style: BlockStyle::Fixed,
position: snapshot.anchor_after(Point::new(2, 0)),
disposition: BlockDisposition::Below,
height: 1,

View file

@ -3,9 +3,9 @@ use super::{
Anchor, DisplayPoint, Editor, EditorMode, EditorSnapshot, Input, Scroll, Select, SelectPhase,
SoftWrap, ToPoint, MAX_LINE_LEN,
};
use crate::hover_popover::HoverAt;
use crate::{
display_map::{DisplaySnapshot, TransformBlock},
display_map::{BlockStyle, DisplaySnapshot, TransformBlock},
hover_popover::HoverAt,
EditorStyle,
};
use clock::ReplicaId;
@ -617,10 +617,13 @@ impl EditorElement {
let scroll_left = scroll_position.x() * layout.em_width;
let scroll_top = scroll_position.y() * layout.line_height;
for (row, element) in &mut layout.blocks {
let origin = bounds.origin()
+ vec2f(-scroll_left, *row as f32 * layout.line_height - scroll_top);
element.paint(origin, visible_bounds, cx);
for block in &mut layout.blocks {
let mut origin =
bounds.origin() + vec2f(0., block.row as f32 * layout.line_height - scroll_top);
if !matches!(block.style, BlockStyle::Sticky) {
origin += vec2f(-scroll_left, 0.);
}
block.element.paint(origin, visible_bounds, cx);
}
}
@ -788,7 +791,8 @@ impl EditorElement {
&mut self,
rows: Range<u32>,
snapshot: &EditorSnapshot,
width: f32,
editor_width: f32,
scroll_width: f32,
gutter_padding: f32,
gutter_width: f32,
em_width: f32,
@ -797,7 +801,7 @@ impl EditorElement {
style: &EditorStyle,
line_layouts: &[text_layout::Line],
cx: &mut LayoutContext,
) -> Vec<(u32, ElementBox)> {
) -> (f32, Vec<BlockLayout>) {
let editor = if let Some(editor) = self.view.upgrade(cx) {
editor
} else {
@ -806,158 +810,189 @@ impl EditorElement {
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
let scroll_x = snapshot.scroll_position.x();
snapshot
let (fixed_blocks, non_fixed_blocks) = snapshot
.blocks_in_range(rows.clone())
.map(|(block_row, block)| {
let mut element = match block {
TransformBlock::Custom(block) => {
let align_to = block
.position()
.to_point(&snapshot.buffer_snapshot)
.to_display_point(snapshot);
let anchor_x = text_x
+ if rows.contains(&align_to.row()) {
line_layouts[(align_to.row() - rows.start) as usize]
.x_for_index(align_to.column() as usize)
} else {
layout_line(align_to.row(), snapshot, style, cx.text_layout_cache)
.x_for_index(align_to.column() as usize)
};
.partition::<Vec<_>, _>(|(_, block)| match block {
TransformBlock::ExcerptHeader { .. } => false,
TransformBlock::Custom(block) => block.style() == BlockStyle::Fixed,
});
let mut render_block = |block: &TransformBlock, width: f32| {
let mut element = match block {
TransformBlock::Custom(block) => {
let align_to = block
.position()
.to_point(&snapshot.buffer_snapshot)
.to_display_point(snapshot);
let anchor_x = text_x
+ if rows.contains(&align_to.row()) {
line_layouts[(align_to.row() - rows.start) as usize]
.x_for_index(align_to.column() as usize)
} else {
layout_line(align_to.row(), snapshot, style, cx.text_layout_cache)
.x_for_index(align_to.column() as usize)
};
cx.render(&editor, |_, cx| {
block.render(&mut BlockContext {
cx,
anchor_x,
gutter_padding,
line_height,
scroll_x,
gutter_width,
em_width,
})
cx.render(&editor, |_, cx| {
block.render(&mut BlockContext {
cx,
anchor_x,
gutter_padding,
line_height,
scroll_x,
gutter_width,
em_width,
})
}
TransformBlock::ExcerptHeader {
key,
buffer,
range,
starts_new_buffer,
..
} => {
let jump_icon = project::File::from_dyn(buffer.file()).map(|file| {
let jump_position = range
.primary
.as_ref()
.map_or(range.context.start, |primary| primary.start);
let jump_action = crate::Jump {
path: ProjectPath {
worktree_id: file.worktree_id(cx),
path: file.path.clone(),
},
position: language::ToPoint::to_point(&jump_position, buffer),
anchor: jump_position,
};
})
}
TransformBlock::ExcerptHeader {
key,
buffer,
range,
starts_new_buffer,
..
} => {
let jump_icon = project::File::from_dyn(buffer.file()).map(|file| {
let jump_position = range
.primary
.as_ref()
.map_or(range.context.start, |primary| primary.start);
let jump_action = crate::Jump {
path: ProjectPath {
worktree_id: file.worktree_id(cx),
path: file.path.clone(),
},
position: language::ToPoint::to_point(&jump_position, buffer),
anchor: jump_position,
};
enum JumpIcon {}
cx.render(&editor, |_, cx| {
MouseEventHandler::new::<JumpIcon, _, _>(*key, cx, |state, _| {
let style = style.jump_icon.style_for(state, false);
Svg::new("icons/jump.svg")
.with_color(style.color)
.constrained()
.with_width(style.icon_width)
.aligned()
.contained()
.with_style(style.container)
.constrained()
.with_width(style.button_width)
.with_height(style.button_width)
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click({
move |_, _, cx| cx.dispatch_action(jump_action.clone())
})
.with_tooltip(
*key,
"Jump to Buffer".to_string(),
Some(Box::new(crate::OpenExcerpts)),
tooltip_style.clone(),
cx,
)
.aligned()
.flex_float()
.boxed()
})
});
let padding = gutter_padding + scroll_x * em_width;
if *starts_new_buffer {
let style = &self.style.diagnostic_path_header;
let font_size =
(style.text_scale_factor * self.style.text.font_size).round();
let mut filename = None;
let mut parent_path = None;
if let Some(file) = buffer.file() {
let path = file.path();
filename =
path.file_name().map(|f| f.to_string_lossy().to_string());
parent_path =
path.parent().map(|p| p.to_string_lossy().to_string() + "/");
}
Flex::row()
.with_child(
Label::new(
filename.unwrap_or_else(|| "untitled".to_string()),
style.filename.text.clone().with_font_size(font_size),
)
.contained()
.with_style(style.filename.container)
enum JumpIcon {}
cx.render(&editor, |_, cx| {
MouseEventHandler::new::<JumpIcon, _, _>(*key, cx, |state, _| {
let style = style.jump_icon.style_for(state, false);
Svg::new("icons/jump.svg")
.with_color(style.color)
.constrained()
.with_width(style.icon_width)
.aligned()
.boxed(),
.contained()
.with_style(style.container)
.constrained()
.with_width(style.button_width)
.with_height(style.button_width)
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(move |_, _, cx| cx.dispatch_action(jump_action.clone()))
.with_tooltip(
*key,
"Jump to Buffer".to_string(),
Some(Box::new(crate::OpenExcerpts)),
tooltip_style.clone(),
cx,
)
.aligned()
.flex_float()
.boxed()
})
});
if *starts_new_buffer {
let style = &self.style.diagnostic_path_header;
let font_size =
(style.text_scale_factor * self.style.text.font_size).round();
let mut filename = None;
let mut parent_path = None;
if let Some(file) = buffer.file() {
let path = file.path();
filename = path.file_name().map(|f| f.to_string_lossy().to_string());
parent_path =
path.parent().map(|p| p.to_string_lossy().to_string() + "/");
}
Flex::row()
.with_child(
Label::new(
filename.unwrap_or_else(|| "untitled".to_string()),
style.filename.text.clone().with_font_size(font_size),
)
.with_children(parent_path.map(|path| {
Label::new(
path,
style.path.text.clone().with_font_size(font_size),
)
.contained()
.with_style(style.filename.container)
.aligned()
.boxed(),
)
.with_children(parent_path.map(|path| {
Label::new(path, style.path.text.clone().with_font_size(font_size))
.contained()
.with_style(style.path.container)
.aligned()
.boxed()
}))
.with_children(jump_icon)
.contained()
.with_style(style.container)
.with_padding_left(padding)
.with_padding_right(padding)
.expanded()
.named("path header block")
} else {
let text_style = self.style.text.clone();
Flex::row()
.with_child(Label::new("".to_string(), text_style).boxed())
.with_children(jump_icon)
.contained()
.with_padding_left(padding)
.with_padding_right(padding)
.expanded()
.named("collapsed context")
}
}))
.with_children(jump_icon)
.contained()
.with_style(style.container)
.with_padding_left(gutter_padding)
.with_padding_right(gutter_padding)
.expanded()
.named("path header block")
} else {
let text_style = self.style.text.clone();
Flex::row()
.with_child(Label::new("".to_string(), text_style).boxed())
.with_children(jump_icon)
.contained()
.with_padding_left(gutter_padding)
.with_padding_right(gutter_padding)
.expanded()
.named("collapsed context")
}
};
}
};
element.layout(
SizeConstraint {
min: Vector2F::zero(),
max: vec2f(width, block.height() as f32 * line_height),
},
cx,
);
(block_row, element)
})
.collect()
element.layout(
SizeConstraint {
min: Vector2F::zero(),
max: vec2f(width, block.height() as f32 * line_height),
},
cx,
);
element
};
let mut fixed_block_max_width = 0f32;
let mut blocks = Vec::new();
for (row, block) in fixed_blocks {
let element = render_block(block, f32::INFINITY);
fixed_block_max_width = fixed_block_max_width.max(element.size().x() + em_width);
blocks.push(BlockLayout {
row,
element,
style: BlockStyle::Fixed,
});
}
for (row, block) in non_fixed_blocks {
let style = match block {
TransformBlock::Custom(block) => block.style(),
TransformBlock::ExcerptHeader { .. } => BlockStyle::Sticky,
};
let width = match style {
BlockStyle::Sticky => editor_width,
BlockStyle::Flex => editor_width
.max(fixed_block_max_width)
.max(gutter_width + scroll_width),
BlockStyle::Fixed => unreachable!(),
};
let element = render_block(block, width);
blocks.push(BlockLayout {
row,
element,
style,
});
}
(
scroll_width.max(fixed_block_max_width - gutter_width),
blocks,
)
}
}
@ -1148,6 +1183,21 @@ impl Element for EditorElement {
.width();
let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.x();
let em_width = style.text.em_width(cx.font_cache);
let (scroll_width, blocks) = self.layout_blocks(
start_row..end_row,
&snapshot,
size.x(),
scroll_width,
gutter_padding,
gutter_width,
em_width,
gutter_width + gutter_margin,
line_height,
&style,
&line_layouts,
cx,
);
let max_row = snapshot.max_point().row();
let scroll_max = vec2f(
((scroll_width - text_size.x()) / em_width).max(0.0),
@ -1246,20 +1296,6 @@ impl Element for EditorElement {
);
}
let blocks = self.layout_blocks(
start_row..end_row,
&snapshot,
size.x().max(scroll_width + gutter_width),
gutter_padding,
gutter_width,
em_width,
gutter_width + gutter_margin,
line_height,
&style,
&line_layouts,
cx,
);
(
size,
LayoutState {
@ -1353,8 +1389,8 @@ impl Element for EditorElement {
}
}
for (_, block) in &mut layout.blocks {
if block.dispatch_event(event, cx) {
for block in &mut layout.blocks {
if block.element.dispatch_event(event, cx) {
return true;
}
}
@ -1440,7 +1476,7 @@ pub struct LayoutState {
highlighted_rows: Option<Range<u32>>,
line_layouts: Vec<text_layout::Line>,
line_number_layouts: Vec<Option<text_layout::Line>>,
blocks: Vec<(u32, ElementBox)>,
blocks: Vec<BlockLayout>,
line_height: f32,
em_width: f32,
em_advance: f32,
@ -1451,6 +1487,12 @@ pub struct LayoutState {
hover: Option<(DisplayPoint, ElementBox)>,
}
struct BlockLayout {
row: u32,
element: ElementBox,
style: BlockStyle,
}
fn layout_line(
row: u32,
snapshot: &EditorSnapshot,
@ -1763,6 +1805,7 @@ mod tests {
editor.set_placeholder_text("hello", cx);
editor.insert_blocks(
[BlockProperties {
style: BlockStyle::Fixed,
disposition: BlockDisposition::Above,
height: 3,
position: Anchor::min(),