rufio 0.63.0 → 0.65.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 903b51d0ee611d29ad7ea8961e7948b9dceae16b6d04280c28252149ae2fdd20
4
- data.tar.gz: 287941ec941e8200c98a881c3ce6de2f7cc7ce3b13e63ba17875ff2222c92bc8
3
+ metadata.gz: c80ee03ac65fdfbf06623c7702795bdbaced929d40e72739f599d43eb8d240ef
4
+ data.tar.gz: 8bc1a41cd84d762923e6f4785542c8ad4de418c804d0b30150d4217dac0168bd
5
5
  SHA512:
6
- metadata.gz: ab00eea888ea729262a4b8c787ecdf43814afe0c573fdc2624597b014027836ae81da4342b413024a10ba6f3212faf5cee1f987af45357fc99134f93d3425314
7
- data.tar.gz: c665a171a03ab2ebe1941d7b38eeac86ccbd1552c3ee5a7e8cf252fa20293470399cf4cf1ecdeacba8d0d6fb251d5cd4b87ae74a4b4e924814d6821c34d99857
6
+ metadata.gz: c42bffef8f63ed300ef17a960625e0e5b5b20735e1f326fba26691e7731c37a0f24a09194b2c51d10e758e4c44710f0e938781e47e0662ba63a1b7afce760195
7
+ data.tar.gz: 7033a73f5a0c124eb9fa722e614a7a28f17972872a4cbe8d7682558ba7760852c3c10db6226e5771222ddb94e1f9a68dbe686f1b621c0dee6ae45073aae47a2a
data/CHANGELOG.md CHANGED
@@ -7,6 +7,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.65.0] - 2026-02-14
11
+
12
+ ### Changed
13
+ - **Display version in title bar**: Changed header format from `💎 rufio - /path` to `💎 rufio v0.65.0 - /path`
14
+
15
+ ## [0.64.0] - 2026-02-07
16
+
17
+ ### Added
18
+ - **Screen Overlay Layer**: Added overlay layer to Screen class for dialog rendering
19
+ - `enable_overlay` / `disable_overlay` / `clear_overlay` / `overlay_enabled?`
20
+ - Draw to overlay layer with `put_overlay` / `put_overlay_string`
21
+ - `row()` method automatically composites overlay and base layers
22
+ - Automatically marks dirty rows when overlay is disabled (guarantees redraw)
23
+ - **`show_overlay_dialog` helper method**: Added unified overlay dialog display method to TerminalUI, KeybindHandler, BookmarkManager, CommandModeUI, and ZoxideIntegration
24
+ - Uses overlay when `terminal_ui` is available, falls back to direct drawing otherwise
25
+ - **`draw_floating_window_to_overlay`**: Added floating window drawing method to DialogRenderer for overlay layer
26
+ - **Screen Overlay tests**: Added `test/test_screen_overlay.rb`
27
+
28
+ ### Changed
29
+ - **Buffer-based dialog rendering**: Changed all dialog rendering to go through Screen overlay
30
+ - BookmarkManager: bookmark menu, list, rename, delete, and confirmation dialogs
31
+ - KeybindHandler: delete confirmation, copy/move confirmation, exit confirmation, script path management, bookmark operation results
32
+ - CommandModeUI: command execution result display
33
+ - ZoxideIntegration: no history message, history selection dialog
34
+ - TerminalUI: help dialog, announcements, plugin loading errors
35
+ - **Overlay-based command mode**: Changed from direct drawing (outside Screen buffer) to overlay-based buffer drawing
36
+ - Integrated into main loop buffer drawing via `draw_command_mode_to_overlay` method
37
+ - **Box drawing character width fix**: Fixed `TextUtils.char_width` to treat box drawing characters (U+2500-U+257F) as width 1 (matching actual terminal display width)
38
+ - **Code style unification**: Unified `require_relative` in `lib/rufio.rb` from double quotes to single quotes
39
+ - **`set_terminal_ui` propagation**: Set `terminal_ui` on BookmarkManager and ZoxideIntegration via KeybindHandler
40
+ - **TerminalUI**: Exposed `screen` and `renderer` `attr_reader`
41
+
42
+ ### Technical Details
43
+ - **New files**: `test/test_screen_overlay.rb`
44
+ - **Modified files**: `lib/rufio.rb`, `lib/rufio/screen.rb`, `lib/rufio/dialog_renderer.rb`, `lib/rufio/terminal_ui.rb`, `lib/rufio/keybind_handler.rb`, `lib/rufio/bookmark_manager.rb`, `lib/rufio/command_mode_ui.rb`, `lib/rufio/text_utils.rb`, `lib/rufio/zoxide_integration.rb`
45
+
10
46
  ## [0.63.0] - 2026-02-01
11
47
 
12
48
  ### Added
@@ -92,28 +128,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
92
128
  ## [0.60.0] - 2026-01-24
93
129
 
94
130
  ### Added
95
- - **⌨️ スクリプト補完機能**: コマンドモードでスクリプトのTab補完が可能に
96
- - `@`プレフィックスでスクリプト専用補完(例: `@bu` + Tab → `@build.sh`)
97
- - 通常補完時も登録済みスクリプトが候補に表示
98
- - `CommandCompletion`が`CommandMode`と連携してスクリプト候補を取得
131
+ - **⌨️ Script Tab Completion**: Tab completion for scripts in command mode
132
+ - `@` prefix for script-specific completion (e.g., `@bu` + Tab → `@build.sh`)
133
+ - Registered scripts also appear in regular completion candidates
134
+ - `CommandCompletion` works with `CommandMode` to retrieve script candidates
99
135
 
100
136
  ### Removed
101
- - **🗑️ プロジェクトモード廃止**: `P`キーで起動するプロジェクトモードを削除
102
- - `lib/rufio/project_mode.rb` - ProjectModeクラス
103
- - `lib/rufio/project_command.rb` - ProjectCommandクラス
104
- - `lib/rufio/project_log.rb` - ProjectLogクラス
105
- - 関連するUI描画メソッド(`draw_project_mode_screen`等)
106
- - 関連するキーハンドリング(`handle_project_mode_key`等)
107
- - 関連するテストファイル
137
+ - **🗑️ Project Mode Removal**: Removed project mode launched by `P` key
138
+ - `lib/rufio/project_mode.rb` - ProjectMode class
139
+ - `lib/rufio/project_command.rb` - ProjectCommand class
140
+ - `lib/rufio/project_log.rb` - ProjectLog class
141
+ - Related UI drawing methods (`draw_project_mode_screen`, etc.)
142
+ - Related key handling (`handle_project_mode_key`, etc.)
143
+ - Related test files
108
144
 
109
145
  ### Changed
110
- - **📋 ヘルプ表示更新**: ヘルプダイアログに`J`キー(Job mode)を追加
111
- - **🧹 コードクリーンアップ**: プロジェクトモード関連の未使用コードを削除
146
+ - **📋 Help Display Update**: Added `J` key (Job mode) to help dialog
147
+ - **🧹 Code Cleanup**: Removed unused code related to project mode
112
148
 
113
149
  ### Technical Details
114
- - **テストカバレッジ**: 684 tests, 2474 assertions (all passing)
115
- - **削除ファイル**: 7ファイル(ライブラリ3、テスト4
116
- - **影響範囲**: `keybind_handler.rb`, `terminal_ui.rb`, `rufio.rb`
150
+ - **Test coverage**: 684 tests, 2474 assertions (all passing)
151
+ - **Deleted files**: 7 files (3 library, 4 test)
152
+ - **Affected files**: `keybind_handler.rb`, `terminal_ui.rb`, `rufio.rb`
117
153
 
118
154
  ## [0.41.0] - 2026-01-13
119
155
 
@@ -10,6 +10,12 @@ module Rufio
10
10
  def initialize(bookmark = nil, dialog_renderer = nil)
11
11
  @bookmark = bookmark || create_default_bookmark
12
12
  @dialog_renderer = dialog_renderer
13
+ @terminal_ui = nil
14
+ end
15
+
16
+ # terminal_ui を設定
17
+ def set_terminal_ui(terminal_ui)
18
+ @terminal_ui = terminal_ui
13
19
  end
14
20
 
15
21
  private
@@ -46,40 +52,45 @@ module Rufio
46
52
 
47
53
  dialog_width = 45
48
54
  dialog_height = 4 + content_lines.length
49
- x, y = @dialog_renderer.calculate_center(dialog_width, dialog_height)
50
-
51
- @dialog_renderer.draw_floating_window(x, y, dialog_width, dialog_height, title, content_lines, {
52
- border_color: "\e[34m", # Blue
53
- title_color: "\e[1;34m", # Bold blue
54
- content_color: "\e[37m" # White
55
- })
56
-
57
- # Wait for key input
58
- loop do
59
- input = STDIN.getch.downcase
60
-
61
- case input
62
- when 'a'
63
- @dialog_renderer.clear_area(x, y, dialog_width, dialog_height)
64
- return { action: :add, path: current_path }
65
- when 'l'
66
- @dialog_renderer.clear_area(x, y, dialog_width, dialog_height)
67
- return { action: :list }
68
- when 'r'
69
- @dialog_renderer.clear_area(x, y, dialog_width, dialog_height)
70
- return { action: :rename }
71
- when 'd'
72
- @dialog_renderer.clear_area(x, y, dialog_width, dialog_height)
73
- return { action: :remove }
74
- when '1', '2', '3', '4', '5', '6', '7', '8', '9'
75
- @dialog_renderer.clear_area(x, y, dialog_width, dialog_height)
76
- return { action: :navigate, number: input.to_i }
77
- else
78
- # Cancel
79
- @dialog_renderer.clear_area(x, y, dialog_width, dialog_height)
80
- return { action: :cancel }
55
+
56
+ result = { action: :cancel }
57
+ show_overlay_dialog(title, content_lines, {
58
+ width: dialog_width,
59
+ height: dialog_height,
60
+ border_color: "\e[34m", # Blue
61
+ title_color: "\e[1;34m", # Bold blue
62
+ content_color: "\e[37m" # White
63
+ }) do
64
+ # Wait for key input
65
+ loop do
66
+ input = STDIN.getch.downcase
67
+
68
+ case input
69
+ when 'a'
70
+ result = { action: :add, path: current_path }
71
+ break
72
+ when 'l'
73
+ result = { action: :list }
74
+ break
75
+ when 'r'
76
+ result = { action: :rename }
77
+ break
78
+ when 'd'
79
+ result = { action: :remove }
80
+ break
81
+ when '1', '2', '3', '4', '5', '6', '7', '8', '9'
82
+ result = { action: :navigate, number: input.to_i }
83
+ break
84
+ else
85
+ # Cancel
86
+ result = { action: :cancel }
87
+ break
88
+ end
81
89
  end
90
+ nil
82
91
  end
92
+
93
+ result
83
94
  end
84
95
 
85
96
  # Add a bookmark interactively
@@ -141,32 +152,33 @@ module Rufio
141
152
  title = 'Rename Bookmark'
142
153
  width = 60
143
154
  height = [content_lines.length + 4, 20].min
144
- x, y = @dialog_renderer.calculate_center(width, height)
145
155
 
146
- @dialog_renderer.draw_floating_window(x, y, width, height, title, content_lines, {
156
+ # Wait for selection
157
+ selected_number = nil
158
+ show_overlay_dialog(title, content_lines, {
159
+ width: width,
160
+ height: height,
147
161
  border_color: "\e[33m", # Yellow
148
162
  title_color: "\e[1;33m", # Bold yellow
149
163
  content_color: "\e[37m" # White
150
- })
151
-
152
- # Wait for selection
153
- selected_number = nil
154
- loop do
155
- input = STDIN.getch.downcase
156
-
157
- if input >= '1' && input <= '9'
158
- number = input.to_i
159
- if number > 0 && number <= bookmarks.length
160
- selected_number = number
164
+ }) do
165
+ loop do
166
+ input = STDIN.getch.downcase
167
+
168
+ if input >= '1' && input <= '9'
169
+ number = input.to_i
170
+ if number > 0 && number <= bookmarks.length
171
+ selected_number = number
172
+ break
173
+ end
174
+ elsif input == "\e" # ESC
161
175
  break
162
176
  end
163
- elsif input == "\e" # ESC
164
- @dialog_renderer.clear_area(x, y, width, height)
165
- return false
166
177
  end
178
+ nil
167
179
  end
168
180
 
169
- @dialog_renderer.clear_area(x, y, width, height)
181
+ return false unless selected_number
170
182
 
171
183
  # Get new name
172
184
  bookmark_to_rename = bookmarks[selected_number - 1]
@@ -216,32 +228,33 @@ module Rufio
216
228
  title = 'Delete Bookmark'
217
229
  width = 60
218
230
  height = [content_lines.length + 4, 20].min
219
- x, y = @dialog_renderer.calculate_center(width, height)
220
231
 
221
- @dialog_renderer.draw_floating_window(x, y, width, height, title, content_lines, {
232
+ # Wait for selection
233
+ selected_number = nil
234
+ show_overlay_dialog(title, content_lines, {
235
+ width: width,
236
+ height: height,
222
237
  border_color: "\e[31m", # Red (warning)
223
238
  title_color: "\e[1;31m", # Bold red
224
239
  content_color: "\e[37m" # White
225
- })
226
-
227
- # Wait for selection
228
- selected_number = nil
229
- loop do
230
- input = STDIN.getch.downcase
231
-
232
- if input >= '1' && input <= '9'
233
- number = input.to_i
234
- if number > 0 && number <= bookmarks.length
235
- selected_number = number
240
+ }) do
241
+ loop do
242
+ input = STDIN.getch.downcase
243
+
244
+ if input >= '1' && input <= '9'
245
+ number = input.to_i
246
+ if number > 0 && number <= bookmarks.length
247
+ selected_number = number
248
+ break
249
+ end
250
+ elsif input == "\e" # ESC
236
251
  break
237
252
  end
238
- elsif input == "\e" # ESC
239
- @dialog_renderer.clear_area(x, y, width, height)
240
- return false
241
253
  end
254
+ nil
242
255
  end
243
256
 
244
- @dialog_renderer.clear_area(x, y, width, height)
257
+ return false unless selected_number
245
258
 
246
259
  # Confirm deletion
247
260
  bookmark_to_remove = bookmarks[selected_number - 1]
@@ -284,31 +297,32 @@ module Rufio
284
297
  title = 'Bookmarks'
285
298
  width = 60
286
299
  height = [content_lines.length + 4, 20].min
287
- x, y = @dialog_renderer.calculate_center(width, height)
288
300
 
289
- @dialog_renderer.draw_floating_window(x, y, width, height, title, content_lines, {
301
+ # Wait for selection
302
+ selected_bookmark = nil
303
+ show_overlay_dialog(title, content_lines, {
304
+ width: width,
305
+ height: height,
290
306
  border_color: "\e[34m", # Blue
291
307
  title_color: "\e[1;34m", # Bold blue
292
308
  content_color: "\e[37m" # White
293
- })
294
-
295
- # Wait for selection
296
- selected_bookmark = nil
297
- loop do
298
- input = STDIN.getch.downcase
299
-
300
- if input >= '1' && input <= '9'
301
- number = input.to_i
302
- if number > 0 && number <= bookmarks.length
303
- selected_bookmark = bookmarks[number - 1]
309
+ }) do
310
+ loop do
311
+ input = STDIN.getch.downcase
312
+
313
+ if input >= '1' && input <= '9'
314
+ number = input.to_i
315
+ if number > 0 && number <= bookmarks.length
316
+ selected_bookmark = bookmarks[number - 1]
317
+ break
318
+ end
319
+ elsif input == "\e" # ESC
304
320
  break
305
321
  end
306
- elsif input == "\e" # ESC
307
- break
308
322
  end
323
+ nil
309
324
  end
310
325
 
311
- @dialog_renderer.clear_area(x, y, width, height)
312
326
  selected_bookmark
313
327
  end
314
328
 
@@ -376,6 +390,48 @@ module Rufio
376
390
 
377
391
  private
378
392
 
393
+ # オーバーレイダイアログを表示してキー入力を待つヘルパーメソッド
394
+ def show_overlay_dialog(title, content_lines, options = {}, &block)
395
+ # terminal_ui が利用可能で、screen と renderer が存在する場合のみオーバーレイを使用
396
+ use_overlay = @terminal_ui &&
397
+ @terminal_ui.respond_to?(:screen) &&
398
+ @terminal_ui.respond_to?(:renderer) &&
399
+ @terminal_ui.screen &&
400
+ @terminal_ui.renderer
401
+
402
+ if use_overlay
403
+ # オーバーレイを使用
404
+ @terminal_ui.show_overlay_dialog(title, content_lines, options, &block)
405
+ else
406
+ # フォールバック: 従来の方法
407
+ width = options[:width]
408
+ height = options[:height]
409
+
410
+ unless width && height
411
+ width, height = @dialog_renderer.calculate_dimensions(content_lines, {
412
+ title: title,
413
+ min_width: options[:min_width] || 40,
414
+ max_width: options[:max_width] || 80
415
+ })
416
+ end
417
+
418
+ x, y = @dialog_renderer.calculate_center(width, height)
419
+
420
+ @dialog_renderer.draw_floating_window(x, y, width, height, title, content_lines, {
421
+ border_color: options[:border_color] || "\e[37m",
422
+ title_color: options[:title_color] || "\e[1;33m",
423
+ content_color: options[:content_color] || "\e[37m"
424
+ })
425
+
426
+ key = block_given? ? yield : STDIN.getch
427
+
428
+ @dialog_renderer.clear_area(x, y, width, height)
429
+ @terminal_ui&.refresh_display
430
+
431
+ key
432
+ end
433
+ end
434
+
379
435
  # Show result dialog with success or error styling
380
436
  # @param title [String] Dialog title
381
437
  # @param message [String] Result message
@@ -401,16 +457,15 @@ module Rufio
401
457
 
402
458
  width = 50
403
459
  height = 6
404
- x, y = @dialog_renderer.calculate_center(width, height)
405
460
 
406
- @dialog_renderer.draw_floating_window(x, y, width, height, title, content_lines, {
461
+ # オーバーレイダイアログを表示
462
+ show_overlay_dialog(title, content_lines, {
463
+ width: width,
464
+ height: height,
407
465
  border_color: border_color,
408
466
  title_color: title_color,
409
467
  content_color: "\e[37m"
410
468
  })
411
-
412
- STDIN.getch
413
- @dialog_renderer.clear_area(x, y, width, height)
414
469
  end
415
470
 
416
471
  # Show confirmation dialog for bookmark removal
@@ -431,30 +486,31 @@ module Rufio
431
486
  title = 'Confirm Delete'
432
487
  width = 50
433
488
  height = content_lines.length + 4
434
- x, y = @dialog_renderer.calculate_center(width, height)
435
489
 
436
- @dialog_renderer.draw_floating_window(x, y, width, height, title, content_lines, {
490
+ # Wait for confirmation
491
+ confirmed = false
492
+ show_overlay_dialog(title, content_lines, {
493
+ width: width,
494
+ height: height,
437
495
  border_color: "\e[33m", # Yellow (warning)
438
496
  title_color: "\e[1;33m", # Bold yellow
439
497
  content_color: "\e[37m" # White
440
- })
498
+ }) do
499
+ loop do
500
+ input = STDIN.getch.downcase
441
501
 
442
- # Wait for confirmation
443
- confirmed = false
444
- loop do
445
- input = STDIN.getch.downcase
446
-
447
- case input
448
- when 'y'
449
- confirmed = true
450
- break
451
- when 'n', "\e" # n or ESC
452
- confirmed = false
453
- break
502
+ case input
503
+ when 'y'
504
+ confirmed = true
505
+ break
506
+ when 'n', "\e" # n or ESC
507
+ confirmed = false
508
+ break
509
+ end
454
510
  end
511
+ nil
455
512
  end
456
513
 
457
- @dialog_renderer.clear_area(x, y, width, height)
458
514
  confirmed
459
515
  end
460
516
  end
@@ -8,10 +8,16 @@ module Rufio
8
8
  def initialize(command_mode, dialog_renderer)
9
9
  @command_mode = command_mode
10
10
  @dialog_renderer = dialog_renderer
11
+ @terminal_ui = nil
11
12
  # 最後に表示したウィンドウの位置とサイズを保存
12
13
  @last_window = nil
13
14
  end
14
15
 
16
+ # terminal_ui を設定
17
+ def set_terminal_ui(terminal_ui)
18
+ @terminal_ui = terminal_ui
19
+ end
20
+
15
21
  # 入力文字列に対する補完候補を取得
16
22
  # @param input [String] 現在の入力文字列
17
23
  # @return [Array<String>] 補完候補の配列
@@ -124,21 +130,14 @@ module Rufio
124
130
  max_width: 100
125
131
  })
126
132
 
127
- # 中央位置を計算
128
- x, y = @dialog_renderer.calculate_center(width, height)
129
-
130
- # フローティングウィンドウを描画
131
- @dialog_renderer.draw_floating_window(x, y, width, height, title, content_lines, {
132
- border_color: border_color,
133
- title_color: title_color,
134
- content_color: content_color
135
- })
136
-
137
- # キー入力を待つ
138
- STDIN.getch
139
-
140
- # ウィンドウをクリア
141
- @dialog_renderer.clear_area(x, y, width, height)
133
+ # オーバーレイダイアログを表示
134
+ show_overlay_dialog(title, content_lines, {
135
+ width: width,
136
+ height: height,
137
+ border_color: border_color,
138
+ title_color: title_color,
139
+ content_color: content_color
140
+ })
142
141
  end
143
142
 
144
143
  # コマンド入力プロンプトをクリア
@@ -156,6 +155,48 @@ module Rufio
156
155
 
157
156
  private
158
157
 
158
+ # オーバーレイダイアログを表示してキー入力を待つヘルパーメソッド
159
+ def show_overlay_dialog(title, content_lines, options = {}, &block)
160
+ # terminal_ui が利用可能で、screen と renderer が存在する場合のみオーバーレイを使用
161
+ use_overlay = @terminal_ui &&
162
+ @terminal_ui.respond_to?(:screen) &&
163
+ @terminal_ui.respond_to?(:renderer) &&
164
+ @terminal_ui.screen &&
165
+ @terminal_ui.renderer
166
+
167
+ if use_overlay
168
+ # オーバーレイを使用
169
+ @terminal_ui.show_overlay_dialog(title, content_lines, options, &block)
170
+ else
171
+ # フォールバック: 従来の方法
172
+ width = options[:width]
173
+ height = options[:height]
174
+
175
+ unless width && height
176
+ width, height = @dialog_renderer.calculate_dimensions(content_lines, {
177
+ title: title,
178
+ min_width: options[:min_width] || 40,
179
+ max_width: options[:max_width] || 80
180
+ })
181
+ end
182
+
183
+ x, y = @dialog_renderer.calculate_center(width, height)
184
+
185
+ @dialog_renderer.draw_floating_window(x, y, width, height, title, content_lines, {
186
+ border_color: options[:border_color] || "\e[37m",
187
+ title_color: options[:title_color] || "\e[1;33m",
188
+ content_color: options[:content_color] || "\e[37m"
189
+ })
190
+
191
+ key = block_given? ? yield : STDIN.getch
192
+
193
+ @dialog_renderer.clear_area(x, y, width, height)
194
+ @terminal_ui&.refresh_display
195
+
196
+ key
197
+ end
198
+ end
199
+
159
200
  # 文字列配列の共通プレフィックスを見つける
160
201
  # @param strings [Array<String>] 文字列配列
161
202
  # @return [String] 共通プレフィックス
@@ -75,6 +75,73 @@ module Rufio
75
75
  screen.put_string(x, bottom_y, "└#{'─' * (width - 2)}┘", fg: border_color)
76
76
  end
77
77
 
78
+ # オーバーレイレイヤーにフローティングウィンドウを描画
79
+ # @param screen [Screen] Screen buffer with overlay enabled
80
+ # @param x [Integer] X position (column)
81
+ # @param y [Integer] Y position (row)
82
+ # @param width [Integer] Window width
83
+ # @param height [Integer] Window height
84
+ # @param title [String, nil] Window title (optional)
85
+ # @param content_lines [Array<String>] Content lines to display
86
+ # @param options [Hash] Customization options
87
+ def draw_floating_window_to_overlay(screen, x, y, width, height, title, content_lines, options = {})
88
+ # オーバーレイが有効でない場合は有効化
89
+ screen.enable_overlay unless screen.overlay_enabled?
90
+
91
+ border_color = options[:border_color] || "\e[37m"
92
+ title_color = options[:title_color] || "\e[1;33m"
93
+ content_color = options[:content_color] || "\e[37m"
94
+
95
+ # Draw top border
96
+ screen.put_overlay_string(x, y, "┌#{'─' * (width - 2)}┐", fg: border_color)
97
+
98
+ # Draw title line if title exists
99
+ if title
100
+ title_width = TextUtils.display_width(title)
101
+ title_padding = (width - 2 - title_width) / 2
102
+ padded_title = ' ' * title_padding + title
103
+ title_line = TextUtils.pad_string_to_width(padded_title, width - 2)
104
+
105
+ screen.put_overlay(x, y + 1, '│', fg: border_color)
106
+ screen.put_overlay_string(x + 1, y + 1, title_line, fg: title_color)
107
+ screen.put_overlay(x + width - 1, y + 1, '│', fg: border_color)
108
+
109
+ # Draw title separator
110
+ screen.put_overlay_string(x, y + 2, "├#{'─' * (width - 2)}┤", fg: border_color)
111
+ content_start_y = y + 3
112
+ else
113
+ content_start_y = y + 1
114
+ end
115
+
116
+ # Draw content lines
117
+ content_height = title ? height - 4 : height - 2
118
+ content_lines.each_with_index do |line, index|
119
+ break if index >= content_height
120
+
121
+ line_y = content_start_y + index
122
+ line_content = TextUtils.pad_string_to_width(line, width - 2)
123
+
124
+ screen.put_overlay(x, line_y, '│', fg: border_color)
125
+ screen.put_overlay_string(x + 1, line_y, line_content, fg: content_color)
126
+ screen.put_overlay(x + width - 1, line_y, '│', fg: border_color)
127
+ end
128
+
129
+ # Fill remaining lines with empty space
130
+ remaining_lines = content_height - content_lines.length
131
+ remaining_lines.times do |i|
132
+ line_y = content_start_y + content_lines.length + i
133
+ empty_line = ' ' * (width - 2)
134
+
135
+ screen.put_overlay(x, line_y, '│', fg: border_color)
136
+ screen.put_overlay_string(x + 1, line_y, empty_line)
137
+ screen.put_overlay(x + width - 1, line_y, '│', fg: border_color)
138
+ end
139
+
140
+ # Draw bottom border
141
+ bottom_y = y + height - 1
142
+ screen.put_overlay_string(x, bottom_y, "└#{'─' * (width - 2)}┘", fg: border_color)
143
+ end
144
+
78
145
  # Draw a floating window with title, content, and customizable colors
79
146
  # @param x [Integer] X position (column)
80
147
  # @param y [Integer] Y position (row)