Class: Canopus::Workspace::View

Inherits:
Zaniah::Element
  • Object
show all
Includes:
TerminalPresentable
Defined in:
lib/canopus/workspace/view.rb,
sig/controller.rbs

Overview

A viewport is one element; only visible document rows become draw commands.

Defined Under Namespace

Modules: TerminalPresentable

Constant Summary

Constants included from TerminalPresentable

TerminalPresentable::ANSI_COLORS

Instance Attribute Summary collapse

Attributes included from TerminalPresentable

#terminal_bounds, #terminal_selection

Instance Method Summary collapse

Methods included from TerminalPresentable

#focus_terminal, #terminal_color, #terminal_command, #terminal_command_jump, #terminal_link_at, #terminal_open_link, #terminal_point, #terminal_scroll, #terminal_select, #terminal_selected_text, #terminal_toggle_command

Constructor Details

#initialize(workspace) ⇒ View

Returns a new instance of View.

Parameters:



8
9
10
11
12
13
14
15
# File 'lib/canopus/workspace/view.rb', line 8

def initialize(workspace)
  super()
  @workspace = workspace
  @editor_bounds, @minimap_bounds, @row_layouts, @regions, @accessibility = {}, {}, {}, [], []
  @project_scroll = 0
  reset_blink
  @revealed_cursors, @line_widths, @code_caches = {}, {}, {}
end

Instance Attribute Details

#accessibility ⇒ Array[{role: Symbol, label: String, bounds: Zaniah::Bounds}] (readonly)

Returns the value of attribute accessibility.

Returns:

  • (Array[{role: Symbol, label: String, bounds: Zaniah::Bounds}])


6
7
8
# File 'lib/canopus/workspace/view.rb', line 6

def accessibility
  @accessibility
end

#editor_bounds ⇒ Hash[Editor, Zaniah::Bounds] (readonly)

Returns the value of attribute editor_bounds.

Returns:

  • (Hash[Editor, Zaniah::Bounds])


6
7
8
# File 'lib/canopus/workspace/view.rb', line 6

def editor_bounds
  @editor_bounds
end

#keymap ⇒ Zaniah::Input::Keymap?

Returns the value of attribute keymap.

Returns:

  • (Zaniah::Input::Keymap, nil)


7
8
9
# File 'lib/canopus/workspace/view.rb', line 7

def keymap
  @keymap
end

#minimap_bounds ⇒ Hash[Editor, Hash[Symbol, untyped]] (readonly)

Returns the value of attribute minimap_bounds.

Returns:

  • (Hash[Editor, Hash[Symbol, untyped]])


6
7
8
# File 'lib/canopus/workspace/view.rb', line 6

def minimap_bounds
  @minimap_bounds
end

#regions ⇒ Array[[Zaniah::Bounds, action]] (readonly)

Returns the value of attribute regions.

Returns:

  • (Array[[Zaniah::Bounds, action]])


6
7
8
# File 'lib/canopus/workspace/view.rb', line 6

def regions
  @regions
end

#row_layouts ⇒ Hash[[Editor, Integer], Zaniah::TextSystem::LineLayout] (readonly)

Returns the value of attribute row_layouts.

Returns:

  • (Hash[[Editor, Integer], Zaniah::TextSystem::LineLayout])


6
7
8
# File 'lib/canopus/workspace/view.rb', line 6

def row_layouts
  @row_layouts
end

Instance Method Details

#cursor_visible? ⇒ Boolean

Returns:

  • (Boolean)


19
# File 'lib/canopus/workspace/view.rb', line 19

def cursor_visible? = @cursor_visible

#hit(point) ⇒ action?

Parameters:

  • point (Zaniah::Point)

Returns:

  • (action, nil)


94
95
96
97
98
# File 'lib/canopus/workspace/view.rb', line 94

def hit(point)
  return unless @painted_palette.equal?(@workspace.palette)
  @regions.reverse_each { |bounds, action| return action if bounds.contains?(point) }
  nil
end

#minimap_scroll(editor, point) ⇒ Boolean

Parameters:

  • editor (Editor)
  • point (Zaniah::Point)

Returns:

  • (Boolean)


112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/canopus/workspace/view.rb', line 112

def minimap_scroll(editor, point)
  entry = @minimap_bounds[editor]
  return false unless entry && @workspace.panes.include?(entry[:pane]) && entry[:pane].active.equal?(editor)

  bounds = entry[:bounds]
  fraction = ((point.y - bounds.y).to_f / [bounds.height, 1].max).clamp(0, 1)
  source_row = (fraction * editor.buffer.line_count).floor.clamp(0, editor.buffer.line_count - 1)
  display_row = editor.display_map.to_display(editor.buffer.rope.line_start(source_row)).row
  target = (display_row - editor.viewport_rows / 2.0).clamp(0, [editor.display_map.row_count - editor.viewport_rows, 0].max)
  editor.scroll(dy: target - editor.scroll_y)
  true
end

#offset_at(editor, point) ⇒ Integer

Parameters:

  • editor (Editor)
  • point (Zaniah::Point)

Returns:

  • (Integer)


99
100
101
102
103
104
105
106
107
108
# File 'lib/canopus/workspace/view.rb', line 99

def offset_at(editor, point)
  bounds = @editor_bounds.fetch(editor)
  row = (((point.y - bounds.y) / @line_height) + editor.scroll_y).floor.clamp(0, editor.display_map.row_count - 1)
  value = editor.display_map.row(row).text
  layout = @row_layouts[[editor, row]] || @cx.text_system&.layout_line(value, size: @font_size)
  x = point.x - bounds.x - gutter(editor) + editor.scroll_x
  byte = layout ? layout.index_for_x(x) : (x / (@font_size * 0.6)).round.clamp(0, value.length)
  column = layout ? value.byteslice(0, byte).length : byte
  editor.display_map.to_buffer(DisplayPoint.new(row, column))
end

#paint(bounds, _state, _prepaint, cx) ⇒ void

This method returns an undefined value.

Parameters:

  • bounds (Zaniah::Bounds)
  • state (Object, nil)
  • prepaint (Object, nil)
  • context (Zaniah::FrameContext)


27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/canopus/workspace/view.rb', line 27

def paint(bounds, _state, _prepaint, cx)
  @cx, @theme, @scene = cx, @workspace.theme, cx.scene
  @children.clear
  @viewport_bounds = bounds
  @painted_palette = @workspace.palette
  @font_size = @workspace.settings["font_size"]
  @line_height = (@font_size * 1.55).ceil
  @line_height = 20 if @cx.window.is_a?(Zaniah::Platform::TUI::Window)
  @frame_id = @frame_id.to_i + 1
  @workspace.minimap.begin_frame(@frame_id, text_system: @cx.text_system, font_size: @font_size,
    font_family: @workspace.settings["font_family"], scale_factor: @cx.window.scale_factor)
  @regions.clear
  @row_layouts.clear
  @overlay_rows = []
  @editor_bounds.clear
  @minimap_bounds.clear
  @accessibility.clear
  visible_editors = @workspace.panes.map(&:active)
  @revealed_cursors.delete_if { |editor, _| !visible_editors.include?(editor) }
  @line_widths.delete_if { |editor, _| !visible_editors.include?(editor) }
  @code_caches.delete_if { |editor, _| !visible_editors.include?(editor) }
  fill(bounds, :background)
  left_panels = drawable_panels(:left)
  bottom_panels = drawable_panels(:bottom)
  bottom_visible = @workspace.docks[:bottom][:visible] && (@workspace.task_output_visible || @workspace.terminal_visible || !bottom_panels.empty?)
  bottom = bottom_visible ? [bounds.height * 0.7, @workspace.docks[:bottom][:size]].min : 0
  left_visible = @workspace.docks[:left][:visible] && (@workspace.show_project || !left_panels.empty?)
  sidebar = left_visible && bounds.width >= 620 ? [@workspace.docks[:left][:size], bounds.width * 0.4].min : 0
  right = @workspace.docks[:right][:visible] && !drawable_panels(:right).empty? && bounds.width >= 620 ? [@workspace.docks[:right][:size], bounds.width * 0.4].min : 0
  body = Zaniah::Bounds.new(sidebar, 0, bounds.width - sidebar - right, [bounds.height - 26 - bottom, 0].max)
  if sidebar.positive?
    project_height = if @workspace.show_project
      left_panels.empty? ? bounds.height - 26 : (bounds.height - 26) * 0.6
    else 0
    end
    paint_project(Zaniah::Bounds.new(0, 0, sidebar, project_height)) if project_height.positive?
    paint_panels(:left, Zaniah::Bounds.new(0, project_height, sidebar, bounds.height - 26 - project_height)) unless left_panels.empty?
  end
  paint_layout(@workspace.layout, body)
  if bottom.positive?
    area = Zaniah::Bounds.new(sidebar, body.height, body.width, bottom)
    if @workspace.task_output_visible
      paint_task_output(area)
    elsif @workspace.terminal_visible
      paint_terminal(area)
    else
      paint_panels(:bottom, area)
    end
  end
  paint_panels(:right, Zaniah::Bounds.new(bounds.width - right, 0, right, bounds.height - 26)) if right.positive?
  region(Zaniah::Bounds.new(sidebar - 3, 0, 6, body.height), role: :separator, label: "Resize left dock", action: [:dock_resize, :left, bounds]) if sidebar.positive?
  region(Zaniah::Bounds.new(bounds.width - right - 3, 0, 6, body.height), role: :separator, label: "Resize right dock", action: [:dock_resize, :right, bounds]) if right.positive?
  region(Zaniah::Bounds.new(body.x, body.bottom - 3, body.width, 6), role: :separator, label: "Resize bottom dock", action: [:dock_resize, :bottom, bounds]) if bottom.positive?
  paint_status(Zaniah::Bounds.new(0, bounds.height - 26, bounds.width, 26))
  paint_hover(bounds) if @workspace.hover_card && !@workspace.hover_card.empty?
  paint_palette(bounds) if @workspace.palette
  paint_notifications(bounds) unless @workspace.notifications.empty?
  if @workspace.performance
    stats = @workspace.performance.statistics
    label = "#{stats[:last_frame_ms]&.round(2) || 'n/a'} ms | #{stats[:last_allocations] || 'n/a'} objects | #{stats[:last_draw_calls] || 'n/a'} draws"
    @scene.layer(2_000_000) do
      area = Zaniah::Bounds.new([bounds.right - 360, 0].max, 3, [bounds.width, 356].min, 24)
      fill(area, :panel)
      text(label, area.x + 8, area.y + 4, color: :accent, size: 11)
    end
  end
end

#project_scroll(delta) ⇒ Numeric

Parameters:

  • delta (Numeric)

Returns:

  • (Numeric)


109
110
111
# File 'lib/canopus/workspace/view.rb', line 109

def project_scroll(delta)
  @project_scroll = [@project_scroll + delta, 0].max
end

This method returns an undefined value.

Parameters:

  • now (Numeric) (defaults to: Process.clock_gettime(Process::CLOCK_MONOTONIC))


16
17
18
# File 'lib/canopus/workspace/view.rb', line 16

def reset_blink(now = Process.clock_gettime(Process::CLOCK_MONOTONIC))
  @blink_started, @cursor_visible = now, true
end

#tick(now = Process.clock_gettime(Process::CLOCK_MONOTONIC)) ⇒ Boolean

Parameters:

  • now (Numeric) (defaults to: Process.clock_gettime(Process::CLOCK_MONOTONIC))

Returns:

  • (Boolean)


20
21
22
23
24
25
26
# File 'lib/canopus/workspace/view.rb', line 20

def tick(now = Process.clock_gettime(Process::CLOCK_MONOTONIC))
  expired = @workspace.expire_notifications(now)
  visible = ((now - @blink_started) / 0.5).floor.even?
  return expired if visible == @cursor_visible
  @cursor_visible = visible
  true
end