charming 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +38 -378
  3. data/lib/charming/application.rb +14 -3
  4. data/lib/charming/{application_model.rb → application_state.rb} +3 -3
  5. data/lib/charming/cli.rb +62 -3
  6. data/lib/charming/controller/class_methods.rb +115 -0
  7. data/lib/charming/controller/command_palette.rb +135 -0
  8. data/lib/charming/controller/component_dispatching.rb +81 -0
  9. data/lib/charming/controller/dispatching.rb +60 -0
  10. data/lib/charming/controller/focus_management.rb +30 -0
  11. data/lib/charming/controller/rendering.rb +127 -0
  12. data/lib/charming/controller/session_state.rb +41 -0
  13. data/lib/charming/controller/sidebar_navigation.rb +111 -0
  14. data/lib/charming/controller.rb +46 -448
  15. data/lib/charming/database_commands.rb +103 -0
  16. data/lib/charming/database_installer.rb +152 -0
  17. data/lib/charming/events/key_event.rb +15 -0
  18. data/lib/charming/events/mouse_event.rb +42 -0
  19. data/lib/charming/events/resize_event.rb +9 -0
  20. data/lib/charming/events/task_event.rb +19 -0
  21. data/lib/charming/events/timer_event.rb +9 -0
  22. data/lib/charming/focus.rb +58 -2
  23. data/lib/charming/generators/app_file_generator.rb +13 -0
  24. data/lib/charming/generators/app_generator.rb +147 -45
  25. data/lib/charming/generators/base.rb +26 -0
  26. data/lib/charming/generators/component_generator.rb +10 -10
  27. data/lib/charming/generators/controller_generator.rb +22 -14
  28. data/lib/charming/generators/model_generator.rb +128 -0
  29. data/lib/charming/generators/name.rb +10 -4
  30. data/lib/charming/generators/screen_generator.rb +84 -52
  31. data/lib/charming/generators/templates/app/Gemfile.template +5 -0
  32. data/lib/charming/generators/templates/app/README.md.template +9 -0
  33. data/lib/charming/generators/templates/app/Rakefile.template +3 -0
  34. data/lib/charming/generators/templates/app/application.template +13 -0
  35. data/lib/charming/generators/templates/app/application_controller.template +19 -0
  36. data/lib/charming/generators/templates/app/application_record.template +7 -0
  37. data/lib/charming/generators/templates/app/application_state.template +6 -0
  38. data/lib/charming/generators/templates/app/database_config.template +12 -0
  39. data/lib/charming/generators/templates/app/executable.template +7 -0
  40. data/lib/charming/generators/templates/app/gemspec.template +6 -0
  41. data/lib/charming/generators/templates/app/home_controller.template +6 -0
  42. data/lib/charming/generators/templates/app/home_state.template +7 -0
  43. data/lib/charming/generators/templates/app/keep.template +0 -0
  44. data/lib/charming/generators/templates/app/layout.template +113 -0
  45. data/lib/charming/generators/templates/app/root_file.template +20 -0
  46. data/lib/charming/generators/templates/app/routes.template +5 -0
  47. data/lib/charming/generators/templates/app/seeds.template +1 -0
  48. data/lib/charming/generators/templates/app/spec_controller.template +17 -0
  49. data/lib/charming/generators/templates/app/spec_helper.template +3 -0
  50. data/lib/charming/generators/templates/app/spec_state.template +17 -0
  51. data/lib/charming/generators/templates/app/spec_view.template +16 -0
  52. data/lib/charming/generators/templates/app/version.template +5 -0
  53. data/lib/charming/generators/templates/app/view.template +21 -0
  54. data/lib/charming/generators/templates/component/component.rb.template +9 -0
  55. data/lib/charming/generators/templates/controller/controller.rb.template +6 -0
  56. data/lib/charming/generators/templates/model/migration.rb.template +9 -0
  57. data/lib/charming/generators/templates/model/model.rb.template +6 -0
  58. data/lib/charming/generators/templates/model/spec.rb.template +9 -0
  59. data/lib/charming/generators/templates/screen/controller.rb.template +7 -0
  60. data/lib/charming/generators/templates/screen/spec_controller.rb.template +17 -0
  61. data/lib/charming/generators/templates/screen/spec_state.rb.template +17 -0
  62. data/lib/charming/generators/templates/screen/spec_view.rb.template +13 -0
  63. data/lib/charming/generators/templates/screen/state.rb.template +7 -0
  64. data/lib/charming/generators/templates/screen/view.rb.template +11 -0
  65. data/lib/charming/generators/templates/view/view.rb.template +11 -0
  66. data/lib/charming/generators/view_generator.rb +26 -13
  67. data/lib/charming/internal/renderer/differential.rb +17 -3
  68. data/lib/charming/internal/renderer/full_repaint.rb +6 -0
  69. data/lib/charming/internal/terminal/adapter.rb +29 -3
  70. data/lib/charming/internal/terminal/key_normalizer.rb +84 -0
  71. data/lib/charming/internal/terminal/memory_backend.rb +28 -1
  72. data/lib/charming/internal/terminal/mouse_parser.rb +81 -0
  73. data/lib/charming/internal/terminal/tty_backend.rb +62 -115
  74. data/lib/charming/presentation/component.rb +10 -0
  75. data/lib/charming/presentation/components/activity_indicator.rb +160 -0
  76. data/lib/charming/presentation/components/command_palette.rb +120 -0
  77. data/lib/charming/presentation/components/empty_state.rb +56 -0
  78. data/lib/charming/presentation/components/form/builder.rb +62 -0
  79. data/lib/charming/presentation/components/form/confirm.rb +69 -0
  80. data/lib/charming/presentation/components/form/field.rb +121 -0
  81. data/lib/charming/presentation/components/form/input.rb +71 -0
  82. data/lib/charming/presentation/components/form/note.rb +41 -0
  83. data/lib/charming/presentation/components/form/select.rb +112 -0
  84. data/lib/charming/presentation/components/form/textarea.rb +86 -0
  85. data/lib/charming/presentation/components/form.rb +156 -0
  86. data/lib/charming/presentation/components/keyboard_handler.rb +58 -0
  87. data/lib/charming/presentation/components/list.rb +132 -0
  88. data/lib/charming/presentation/components/markdown.rb +31 -0
  89. data/lib/charming/presentation/components/modal.rb +64 -0
  90. data/lib/charming/presentation/components/progressbar.rb +70 -0
  91. data/lib/charming/presentation/components/spinner.rb +49 -0
  92. data/lib/charming/presentation/components/table.rb +143 -0
  93. data/lib/charming/presentation/components/text_area.rb +267 -0
  94. data/lib/charming/presentation/components/text_input.rb +129 -0
  95. data/lib/charming/presentation/components/viewport.rb +272 -0
  96. data/lib/charming/presentation/layout/builder.rb +86 -0
  97. data/lib/charming/presentation/layout/overlay.rb +57 -0
  98. data/lib/charming/presentation/layout/pane.rb +145 -0
  99. data/lib/charming/presentation/layout/rect.rb +23 -0
  100. data/lib/charming/presentation/layout/screen_layout.rb +60 -0
  101. data/lib/charming/presentation/layout/split.rb +134 -0
  102. data/lib/charming/presentation/layout.rb +43 -0
  103. data/lib/charming/presentation/markdown/block_renderers.rb +120 -0
  104. data/lib/charming/presentation/markdown/inline_renderers.rb +68 -0
  105. data/lib/charming/presentation/markdown/render_context.rb +22 -0
  106. data/lib/charming/presentation/markdown/renderer.rb +113 -0
  107. data/lib/charming/presentation/markdown/syntax_highlighter.rb +79 -0
  108. data/lib/charming/presentation/markdown.rb +11 -0
  109. data/lib/charming/presentation/template_view.rb +34 -0
  110. data/lib/charming/presentation/templates/erb_handler.rb +15 -0
  111. data/lib/charming/presentation/templates.rb +68 -0
  112. data/lib/charming/presentation/ui/ansi_codes.rb +89 -0
  113. data/lib/charming/presentation/ui/ansi_slicer.rb +94 -0
  114. data/lib/charming/presentation/ui/border.rb +35 -0
  115. data/lib/charming/presentation/ui/border_painter.rb +58 -0
  116. data/lib/charming/presentation/ui/canvas.rb +82 -0
  117. data/lib/charming/presentation/ui/style.rb +213 -0
  118. data/lib/charming/presentation/ui/theme.rb +180 -0
  119. data/lib/charming/{ui → presentation/ui}/themes/phosphor.json +2 -2
  120. data/lib/charming/presentation/ui/width.rb +26 -0
  121. data/lib/charming/presentation/ui.rb +91 -0
  122. data/lib/charming/presentation/view.rb +135 -0
  123. data/lib/charming/runtime.rb +9 -7
  124. data/lib/charming/screen.rb +5 -1
  125. data/lib/charming/tasks/inline_executor.rb +37 -0
  126. data/lib/charming/tasks/task.rb +12 -0
  127. data/lib/charming/tasks/threaded_executor.rb +51 -0
  128. data/lib/charming/version.rb +1 -1
  129. data/lib/charming.rb +17 -0
  130. metadata +170 -36
  131. data/lib/charming/component.rb +0 -8
  132. data/lib/charming/components/activity_indicator.rb +0 -158
  133. data/lib/charming/components/command_palette.rb +0 -118
  134. data/lib/charming/components/keyboard_handler.rb +0 -22
  135. data/lib/charming/components/list.rb +0 -105
  136. data/lib/charming/components/modal.rb +0 -48
  137. data/lib/charming/components/progressbar.rb +0 -55
  138. data/lib/charming/components/spinner.rb +0 -37
  139. data/lib/charming/components/table.rb +0 -115
  140. data/lib/charming/components/text_input.rb +0 -103
  141. data/lib/charming/components/viewport.rb +0 -191
  142. data/lib/charming/generators/app_generator/app_spec_templates.rb +0 -86
  143. data/lib/charming/generators/app_generator/basic_templates.rb +0 -69
  144. data/lib/charming/generators/app_generator/component_templates.rb +0 -36
  145. data/lib/charming/generators/app_generator/controller_template.rb +0 -69
  146. data/lib/charming/generators/app_generator/layout_template.rb +0 -160
  147. data/lib/charming/generators/app_generator/model_templates.rb +0 -30
  148. data/lib/charming/generators/app_generator/screen_spec_templates.rb +0 -70
  149. data/lib/charming/generators/app_generator/view_template.rb +0 -90
  150. data/lib/charming/key_event.rb +0 -13
  151. data/lib/charming/mouse_event.rb +0 -40
  152. data/lib/charming/resize_event.rb +0 -7
  153. data/lib/charming/task.rb +0 -7
  154. data/lib/charming/task_event.rb +0 -17
  155. data/lib/charming/task_executor.rb +0 -62
  156. data/lib/charming/timer_event.rb +0 -7
  157. data/lib/charming/ui/border.rb +0 -33
  158. data/lib/charming/ui/style.rb +0 -244
  159. data/lib/charming/ui/theme.rb +0 -178
  160. data/lib/charming/ui/width.rb +0 -24
  161. data/lib/charming/ui.rb +0 -230
  162. data/lib/charming/view.rb +0 -116
  163. /data/lib/charming/{generators.rb → generators/error.rb} +0 -0
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Charming
4
- module Generators
5
- class AppGenerator
6
- module ComponentTemplates
7
- def component
8
- %(# frozen_string_literal: true
9
-
10
- module #{name.class_name}
11
- class AppFrameComponent < Charming::Component
12
- def render
13
- column(title_line, help_line, gap: 1)
14
- end
15
- #{component_helpers}
16
- end
17
- end
18
- )
19
- end
20
-
21
- def component_helpers
22
- %(
23
- private
24
-
25
- def title_line
26
- text title, style: theme.title
27
- end
28
-
29
- def help_line
30
- text "Press p for commands, q to quit.", style: theme.muted
31
- end)
32
- end
33
- end
34
- end
35
- end
36
- end
@@ -1,69 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Charming
4
- module Generators
5
- class AppGenerator
6
- module ControllerTemplate
7
- def application_controller
8
- %(# frozen_string_literal: true
9
-
10
- module #{name.class_name}
11
- class ApplicationController < Charming::Controller
12
- layout Layouts::Application
13
- focus_ring :sidebar, :content
14
-
15
- key "p", :open_command_palette, scope: :global
16
- key "q", :quit, scope: :global
17
-
18
- command "Home" do
19
- navigate_to "/"
20
- end
21
-
22
- command "Theme", :open_theme_palette
23
- command "Close palette", :close_command_palette
24
- command "Quit app", :quit
25
- end
26
- end
27
- )
28
- end
29
-
30
- def controller
31
- %(# frozen_string_literal: true
32
-
33
- module #{name.class_name}
34
- class HomeController < ApplicationController
35
- #{controller_actions}
36
- #{controller_helpers}
37
- end
38
- end
39
- )
40
- end
41
-
42
- def controller_actions
43
- %(
44
- def show
45
- render_home
46
- end)
47
- end
48
-
49
- def controller_helpers
50
- %(
51
-
52
- private
53
- #{render_helpers})
54
- end
55
-
56
- def render_helpers
57
- %(
58
- def render_home
59
- render HomeView.new(home: home, palette: command_palette, screen: screen, theme: theme)
60
- end
61
-
62
- def home
63
- model(:home, HomeModel)
64
- end)
65
- end
66
- end
67
- end
68
- end
69
- end
@@ -1,160 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Charming
4
- module Generators
5
- class AppGenerator
6
- module LayoutTemplate
7
- def layout
8
- %(# frozen_string_literal: true
9
-
10
- module #{name.class_name}
11
- module Layouts
12
- class Application < Charming::View
13
- def render
14
- body = Charming::UI.place(app_frame, width: screen.width, height: screen.height)
15
- return body unless palette
16
-
17
- Charming::UI.overlay(body, command_palette_modal)
18
- end
19
- #{layout_helpers}
20
- end
21
- end
22
- end
23
- )
24
- end
25
-
26
- def layout_helpers
27
- %(
28
- private
29
- #{layout_frame_helpers}
30
- #{layout_navigation_helpers}
31
- #{layout_modal_helpers}
32
- #{layout_style_helpers})
33
- end
34
-
35
- def layout_frame_helpers
36
- %(
37
- def app_frame
38
- narrow? ? column(sidebar, main_content, gap: 1) : row(sidebar, main_content, gap: 1)
39
- end
40
-
41
- def sidebar
42
- box(column(app_title, navigation, shortcuts, gap: 1), style: sidebar_style)
43
- end
44
-
45
- def main_content
46
- box(yield_content, style: main_content_style)
47
- end)
48
- end
49
-
50
- def layout_navigation_helpers
51
- %(
52
-
53
- def app_title
54
- text "#{name.class_name}", style: theme.header_accent.align(:center).width(sidebar_width)
55
- end
56
-
57
- def navigation
58
- column(*nav_items)
59
- end
60
-
61
- def nav_items
62
- controller.application.routes.all.each_with_index.map do |route, index|
63
- text nav_label(route, index), style: nav_style(route, index)
64
- end
65
- end
66
-
67
- def nav_label(route, index)
68
- cursor = sidebar_focused? && index == sidebar_index ? ">" : " "
69
- active = current_route?(route) ? "●" : " "
70
- "\#{cursor} \#{active} \#{route.title}"
71
- end
72
-
73
- def nav_style(route, index)
74
- return theme.selected if sidebar_focused? && index == sidebar_index
75
- return theme.title if current_route?(route)
76
-
77
- theme.muted
78
- end
79
-
80
- def shortcuts
81
- text "tab focus\\np commands\\nq quit", style: theme.muted
82
- end
83
-
84
- def sidebar_focused?
85
- controller.sidebar_focused?
86
- end
87
-
88
- def content_focused?
89
- controller.content_focused?
90
- end
91
-
92
- def sidebar_index
93
- controller.sidebar_index
94
- end
95
-
96
- def current_route?(route)
97
- route.controller_class == controller.class && route.action == :show
98
- end)
99
- end
100
-
101
- def layout_modal_helpers
102
- %(
103
-
104
- def command_palette_modal
105
- render_component Charming::Components::Modal.new(
106
- content: palette,
107
- title: "Command palette",
108
- help: "Type to filter. Enter selects. Escape closes.",
109
- width: 52,
110
- theme: theme
111
- )
112
- end)
113
- end
114
-
115
- def layout_style_helpers
116
- %(
117
- #{layout_frame_style_helpers}
118
- #{layout_dimension_helpers})
119
- end
120
-
121
- def layout_frame_style_helpers
122
- %(
123
- def sidebar_style
124
- base = sidebar_focused? ? theme.title : theme.border
125
- base = base.border(:rounded).padding(1, 2).width(sidebar_width).height(panel_height)
126
- palette ? base.faint : base
127
- end
128
-
129
- def main_content_style
130
- base = content_focused? ? theme.title : theme.border
131
- base = base.border(:rounded).padding(1, 2).width(main_content_width).height(panel_height)
132
- palette ? base.faint : base
133
- end)
134
- end
135
-
136
- def layout_dimension_helpers
137
- %(
138
-
139
- def narrow?
140
- screen.width < 72 && screen.height >= 20
141
- end
142
-
143
- def sidebar_width
144
- narrow? ? [screen.width - 6, 20].max : 22
145
- end
146
-
147
- def main_content_width
148
- narrow? ? [screen.width - 6, 20].max : [screen.width - sidebar_width - 13, 20].max
149
- end
150
-
151
- def panel_height
152
- return nil if narrow?
153
-
154
- [screen.height - 4, 5].max
155
- end)
156
- end
157
- end
158
- end
159
- end
160
- end
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Charming
4
- module Generators
5
- class AppGenerator
6
- module ModelTemplates
7
- def application_model
8
- %(# frozen_string_literal: true
9
-
10
- module #{name.class_name}
11
- class ApplicationModel < Charming::ApplicationModel
12
- end
13
- end
14
- )
15
- end
16
-
17
- def home_model
18
- %(# frozen_string_literal: true
19
-
20
- module #{name.class_name}
21
- class HomeModel < ApplicationModel
22
- attribute :title, :string, default: "#{name.class_name}"
23
- end
24
- end
25
- )
26
- end
27
- end
28
- end
29
- end
30
- end
@@ -1,70 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Charming
4
- module Generators
5
- class AppGenerator
6
- module ScreenSpecTemplates
7
- def spec_model
8
- %(# frozen_string_literal: true
9
-
10
- require "#{app_name.snake_name}"
11
-
12
- RSpec.describe #{app_name.class_name}::#{name.class_name}Model do
13
- describe "#title" do
14
- it "has the correct default string value" do
15
- instance = described_class.new
16
- expect(instance.title).to eq("#{name.class_name}")
17
- end
18
-
19
- it "accepts overridden title values" do
20
- instance = described_class.new(title: "Alternative")
21
- expect(instance.title).to eq("Alternative")
22
- end
23
- end
24
- end
25
- )
26
- end
27
-
28
- def spec_controller
29
- %(# frozen_string_literal: true
30
-
31
- require "#{app_name.snake_name}"
32
-
33
- RSpec.describe #{app_name.class_name}::#{name.controller_class_name} do
34
- let(:application) { #{app_name.class_name}::Application.new }
35
-
36
- subject(:controller) { described_class.new(application: application) }
37
-
38
- describe "#show" do
39
- it "renders the view with the model" do
40
- response = controller.dispatch(:show)
41
-
42
- expect(response).to respond_to(:body)
43
- end
44
- end
45
- end
46
- )
47
- end
48
-
49
- def spec_view
50
- %(# frozen_string_literal: true
51
-
52
- require "#{app_name.snake_name}"
53
-
54
- RSpec.describe #{app_name.class_name}::#{name.view_class_name} do
55
- describe "#render" do
56
- it "renders the model title" do
57
- view = described_class.new(
58
- #{name.snake_name}: double(title: "#{name.class_name}")
59
- )
60
-
61
- expect(view.render).to eq("#{name.class_name}")
62
- end
63
- end
64
- end
65
- )
66
- end
67
- end
68
- end
69
- end
70
- end
@@ -1,90 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Charming
4
- module Generators
5
- class AppGenerator
6
- module ViewTemplate
7
- def executable
8
- %(#!/usr/bin/env ruby
9
- # frozen_string_literal: true
10
-
11
- require "bundler/setup"
12
- require "#{name.snake_name}"
13
-
14
- Charming.run(#{name.class_name}::Application.new)
15
- )
16
- end
17
-
18
- def root_file
19
- %(# frozen_string_literal: true
20
-
21
- require "charming"
22
- require "zeitwerk"
23
-
24
- module #{name.class_name}
25
- end
26
-
27
- loader = Zeitwerk::Loader.new
28
- loader.tag = "#{name.snake_name}"
29
- loader.inflector.inflect("version" => "VERSION")
30
- loader.push_dir(File.expand_path("#{name.snake_name}", __dir__), namespace: #{name.class_name})
31
- loader.push_dir(File.expand_path("../app/models", __dir__), namespace: #{name.class_name})
32
- loader.push_dir(File.expand_path("../app/components", __dir__), namespace: #{name.class_name})
33
- loader.push_dir(File.expand_path("../app/views", __dir__), namespace: #{name.class_name})
34
- loader.push_dir(File.expand_path("../app/controllers", __dir__), namespace: #{name.class_name})
35
- loader.setup
36
-
37
- require_relative "../config/routes"
38
- )
39
- end
40
-
41
- def application
42
- %(# frozen_string_literal: true
43
-
44
- module #{name.class_name}
45
- class Application < Charming::Application
46
- Charming::UI::Theme.built_in_names.each do |theme_name|
47
- theme theme_name.to_sym, built_in: theme_name
48
- end
49
-
50
- default_theme :phosphor
51
- end
52
- end
53
- )
54
- end
55
-
56
- def version
57
- %(# frozen_string_literal: true
58
-
59
- module #{name.class_name}
60
- VERSION = "0.1.0"
61
- end
62
- )
63
- end
64
-
65
- def view
66
- %(# frozen_string_literal: true
67
-
68
- module #{name.class_name}
69
- class HomeView < Charming::View
70
- def render
71
- app_frame
72
- end
73
- #{view_helpers}
74
- end
75
- end
76
- )
77
- end
78
-
79
- def view_helpers
80
- %(
81
- private
82
-
83
- def app_frame
84
- render_component AppFrameComponent.new(title: home.title, theme: theme)
85
- end)
86
- end
87
- end
88
- end
89
- end
90
- end
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Charming
4
- # KeyEvent represents a terminal key press parsed by the backend. *key* is the normalized semantic
5
- # action name (e.g., `:up`, `:down`, `:q`), while *char*, *ctrl*, *alt*, and *shift* capture raw
6
- # input details for custom bindings.
7
- KeyEvent = Data.define(:key, :char, :ctrl, :alt, :shift) do
8
- # Constructs a key event with the required *key* symbol, plus optional *char* string and modifier booleans.
9
- def initialize(key:, char: nil, ctrl: false, alt: false, shift: false)
10
- super(key: key.to_sym, char: char, ctrl: ctrl, alt: alt, shift: shift)
11
- end
12
- end
13
- end
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Charming
4
- # MOUSE_BUTTON_MAP encodes terminal mouse button codes to semantic symbols. The constant is frozen and private.
5
- MOUSE_BUTTON_MAP = {
6
- 0 => :left, 1 => :middle, 2 => :right, 3 => :release,
7
- 64 => :scroll_up, 65 => :scroll_down,
8
- 66 => :scroll_up, 67 => :scroll_down
9
- }.freeze
10
- private_constant :MOUSE_BUTTON_MAP
11
-
12
- # MouseEvent represents a mouse input event. *button* encodes which button or action was triggered (left,
13
- # right, scroll), while *x* and *y* provide the cursor position. Modifier booleans (*ctrl*, *alt*, *shift*)
14
- # capture key state at the time of the event.
15
- MouseEvent = Data.define(:button, :x, :y, :ctrl, :alt, :shift) do
16
- def initialize(button:, x:, y:, ctrl: false, alt: false, shift: false)
17
- super
18
- end
19
-
20
- # Returns the semantic symbol for *button* — one of `left`, `right`, `scroll_up`, etc. or `:unknown`.
21
- def button_name
22
- MOUSE_BUTTON_MAP.fetch(button, :unknown)
23
- end
24
-
25
- # Returns `true` when the current event is a click (left, middle, or right button).
26
- def click?
27
- %i[left middle right].include?(button_name)
28
- end
29
-
30
- # Returns `true` when the button name maps to either direction of scroll.
31
- def scroll?
32
- %i[scroll_up scroll_down].include?(button_name)
33
- end
34
-
35
- # Returns `true` when the current event is a mouse release action.
36
- def release?
37
- button_name == :release
38
- end
39
- end
40
- end
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Charming
4
- # ResizeEvent represents a terminal window resize. *width* and *height* carry the new terminal dimensions
5
- # in screen cells, replacing the previous Screen dimensions for all subsequent rendering.
6
- ResizeEvent = Data.define(:width, :height)
7
- end
data/lib/charming/task.rb DELETED
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Charming
4
- Task = Data.define(:name, :block) do
5
- def call = block.call
6
- end
7
- end
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Charming
4
- # TaskEvent represents background task completion. *name* is the declared task identifier, *value* carries
5
- # the return result and *error* captures any exception raised during execution. The `error?` predicate
6
- # simplifies error handling in controller handlers.
7
- TaskEvent = Data.define(:name, :value, :error) do
8
- def initialize(name:, value: nil, error: nil)
9
- super
10
- end
11
-
12
- # Returns `true` when the task finished with a non-nil exception.
13
- def error?
14
- !error.nil?
15
- end
16
- end
17
- end
@@ -1,62 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Charming
4
- module TaskExecutor
5
- class Threaded
6
- def initialize(queue)
7
- @queue = queue
8
- @threads = []
9
- @mutex = Mutex.new
10
- end
11
-
12
- def submit(name, &block)
13
- task = Task.new(name: name.to_sym, block: block)
14
- thread = Thread.new(task) { |t| @queue << run(t) }
15
- @mutex.synchronize { @threads << thread }
16
- nil
17
- end
18
-
19
- def shutdown(timeout: 0.0)
20
- threads = @mutex.synchronize { @threads.dup }
21
- threads.each { |thread| thread.join(timeout) }
22
- threads.each do |thread|
23
- next unless thread.alive?
24
-
25
- thread.kill
26
- thread.join(0)
27
- end
28
- end
29
-
30
- private
31
-
32
- def run(task)
33
- TaskEvent.new(name: task.name, value: task.call)
34
- rescue StandardError, ScriptError => e
35
- TaskEvent.new(name: task.name, error: e)
36
- end
37
- end
38
-
39
- class Inline
40
- def initialize(queue)
41
- @queue = queue
42
- end
43
-
44
- def submit(name, &block)
45
- task = Task.new(name: name.to_sym, block: block)
46
- @queue << run(task)
47
- nil
48
- end
49
-
50
- def shutdown(timeout: 0.0)
51
- end
52
-
53
- private
54
-
55
- def run(task)
56
- TaskEvent.new(name: task.name, value: task.call)
57
- rescue StandardError, ScriptError => e
58
- TaskEvent.new(name: task.name, error: e)
59
- end
60
- end
61
- end
62
- end
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Charming
4
- # TimerEvent represents a timed dispatch from the runtime loop. *name* is the declared timer identifier;
5
- # *now* is the monotonically rising clock value at emission for throttle comparisons.
6
- TimerEvent = Data.define(:name, :now)
7
- end
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Charming
4
- module UI
5
- class Border
6
- attr_reader :top_left, :top_right, :bottom_left, :bottom_right, :horizontal, :vertical
7
-
8
- def initialize(corners:, edges:)
9
- @top_left, @top_right, @bottom_left, @bottom_right = corners
10
- @horizontal, @vertical = edges
11
- end
12
-
13
- def self.fetch(name)
14
- STYLES.fetch(name.to_sym)
15
- end
16
- end
17
-
18
- Border::STYLES = {
19
- normal: Border.new(
20
- corners: ["+", "+", "+", "+"], edges: ["-", "|"]
21
- ),
22
- rounded: Border.new(
23
- corners: ["╭", "╮", "╰", "╯"], edges: ["─", "│"]
24
- ),
25
- thick: Border.new(
26
- corners: ["┏", "┓", "┗", "┛"], edges: ["━", "┃"]
27
- ),
28
- double: Border.new(
29
- corners: ["╔", "╗", "╚", "╝"], edges: ["═", "║"]
30
- )
31
- }.freeze
32
- end
33
- end