potty 0.0.1 → 0.0.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.
@@ -1,28 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'curses'
4
3
  require_relative 'base'
5
4
  require_relative '../keys'
5
+ require_relative '../line_editor'
6
6
 
7
7
  module Potty
8
8
  module Widgets
9
9
  # Single-line editable text field. Shows a block cursor when focused,
10
10
  # a dim placeholder when empty and unfocused, and scrolls horizontally
11
- # when the text outgrows the field.
11
+ # when the text outgrows the field. The editing model is a LineEditor
12
+ # (shared with the list InputItem); this widget owns rendering + scroll.
12
13
  #
13
14
  # Emits :change(text) on every edit. ASCII input only for now (matches
14
15
  # the rest of the framework); UTF-8 entry would need multibyte getch.
15
16
  class TextInput < Base
16
- attr_reader :text
17
- attr_accessor :placeholder, :max_length, :on_change
17
+ attr_accessor :placeholder, :on_change
18
18
 
19
19
  def initialize(app, text: '', placeholder: '', max_length: nil, on_change: nil)
20
20
  super(app)
21
- @text = text.dup
21
+ @editor = LineEditor.new(text, max_length: max_length)
22
22
  @placeholder = placeholder
23
- @max_length = max_length
24
23
  @on_change = on_change
25
- @cursor = @text.length
26
24
  @scroll = 0
27
25
  end
28
26
 
@@ -30,32 +28,36 @@ module Potty
30
28
  true
31
29
  end
32
30
 
31
+ def text
32
+ @editor.text
33
+ end
34
+
33
35
  def text=(value)
34
- @text = value.to_s.dup
35
- @cursor = [@cursor, @text.length].min
36
+ @editor.text = value
36
37
  notify_change
37
38
  end
38
39
 
40
+ def max_length
41
+ @editor.max_length
42
+ end
43
+
44
+ def max_length=(value)
45
+ @editor.max_length = value
46
+ end
47
+
39
48
  def preferred_height(_width)
40
49
  1
41
50
  end
42
51
 
43
52
  def handle_key(ch)
44
53
  case ch
45
- when Keys::LEFT
46
- @cursor = [@cursor - 1, 0].max
47
- when Keys::RIGHT
48
- @cursor = [@cursor + 1, @text.length].min
49
- when Keys::HOME, Keys::CTRL_A
50
- @cursor = 0
51
- when Keys::END_, Keys::CTRL_E
52
- @cursor = @text.length
53
- when Keys::DEL_ASCII, Keys::CTRL_H, Keys::BACKSPACE
54
- backspace
55
- when Keys::DELETE, Keys::CTRL_D
56
- delete_forward
57
- when Keys::SPACE..(Keys::DEL_ASCII - 1)
58
- insert(ch.chr)
54
+ when Keys::LEFT then @editor.left
55
+ when Keys::RIGHT then @editor.right
56
+ when Keys::HOME, Keys::CTRL_A then @editor.home
57
+ when Keys::END_, Keys::CTRL_E then @editor.to_end
58
+ when Keys::DEL_ASCII, Keys::CTRL_H, Keys::BACKSPACE then changed(@editor.backspace)
59
+ when Keys::DELETE, Keys::CTRL_D then changed(@editor.delete_forward)
60
+ when Keys::SPACE..(Keys::DEL_ASCII - 1) then changed(@editor.insert(ch.chr))
59
61
  else
60
62
  return false
61
63
  end
@@ -68,68 +70,50 @@ module Potty
68
70
  width = @rect.width
69
71
  adjust_scroll(width)
70
72
 
71
- if @text.empty? && !@focused
73
+ if text.empty? && !@focused
72
74
  window.setpos(@rect.y, @rect.x)
73
- window.attron(theme[:dim]) do
75
+ window.attron(theme.style(:dim)) do
74
76
  window.addstr(@placeholder.to_s[0, width].to_s.ljust(width))
75
77
  end
76
78
  return
77
79
  end
78
80
 
79
- visible = (@text[@scroll, width] || '').ljust(width)
81
+ visible = (text[@scroll, width] || '').ljust(width)
80
82
  window.setpos(@rect.y, @rect.x)
81
- window.attron(theme[:normal]) { window.addstr(visible) }
83
+ window.attron(theme.style(:normal)) { window.addstr(visible) }
82
84
 
83
85
  return unless @focused
84
86
 
85
87
  # Block cursor: reverse-video the cell under the caret.
86
- col = @cursor - @scroll
88
+ col = @editor.cursor - @scroll
87
89
  return if col.negative? || col >= width
88
90
 
89
- char_under = @text[@cursor] || ' '
91
+ char_under = text[@editor.cursor] || ' '
90
92
  window.setpos(@rect.y, @rect.x + col)
91
- window.attron(theme[:normal] | ::Curses::A_REVERSE) do
93
+ window.attron(theme.style(:normal, reverse: true)) do
92
94
  window.addstr(char_under)
93
95
  end
94
96
  end
95
97
 
96
98
  private
97
99
 
98
- def insert(str)
99
- return if @max_length && @text.length >= @max_length
100
-
101
- @text.insert(@cursor, str)
102
- @cursor += str.length
103
- notify_change
104
- end
105
-
106
- def backspace
107
- return if @cursor.zero?
108
-
109
- @text.slice!(@cursor - 1)
110
- @cursor -= 1
111
- notify_change
112
- end
113
-
114
- def delete_forward
115
- return if @cursor >= @text.length
116
-
117
- @text.slice!(@cursor)
118
- notify_change
100
+ def changed(did_change)
101
+ notify_change if did_change
119
102
  end
120
103
 
121
104
  def adjust_scroll(width)
122
105
  return if width <= 0
123
106
 
124
- @scroll = @cursor - width + 1 if @cursor - @scroll >= width
125
- @scroll = @cursor if @cursor < @scroll
107
+ cursor = @editor.cursor
108
+ @scroll = cursor - width + 1 if cursor - @scroll >= width
109
+ @scroll = cursor if cursor < @scroll
126
110
  @scroll = [@scroll, 0].max
127
111
  end
128
112
 
129
113
  def notify_change
130
114
  # Hand listeners a snapshot, not the live internal buffer, so a
131
115
  # consumer that stores the value doesn't see it mutate underfoot.
132
- snapshot = @text.dup
116
+ snapshot = text.dup
133
117
  @on_change&.call(snapshot)
134
118
  emit(:change, snapshot)
135
119
  end
@@ -55,7 +55,7 @@ module Potty
55
55
 
56
56
  knob = @value ? "[\u25CF]" : "[\u25CB]"
57
57
  text = "#{knob} #{@label}"[0, @rect.width]
58
- attr = @focused ? theme.attr(:selected, bold: true) : theme[:normal]
58
+ attr = @focused ? theme.style(:selected, bold: true) : theme.style(:normal)
59
59
 
60
60
  window.setpos(@rect.y, @rect.x)
61
61
  window.attron(attr) { window.addstr(text) }
@@ -3,53 +3,30 @@
3
3
  require 'curses'
4
4
 
5
5
  module Potty
6
- # Manages curses window lifecycle and refresh coordination
6
+ # Holds the curses stdscr and screen dimensions, and batches the
7
+ # refresh (noutrefresh + doupdate). Backs CursesSurface.
7
8
  class WindowManager
8
9
  attr_reader :stdscr, :max_y, :max_x
9
10
 
10
11
  def initialize
11
- @windows = {}
12
12
  @stdscr = nil
13
13
  end
14
14
 
15
- # Called during application setup
15
+ # Called during application setup.
16
16
  def setup(stdscr)
17
17
  @stdscr = stdscr
18
18
  update_dimensions
19
19
  end
20
20
 
21
21
  def update_dimensions
22
- @max_y, @max_x = @stdscr.maxy, @stdscr.maxx
22
+ @max_y = @stdscr.maxy
23
+ @max_x = @stdscr.maxx
23
24
  end
24
25
 
25
- # Create a new window
26
- def create_window(name, height, width, y, x)
27
- win = ::Curses::Window.new(height, width, y, x)
28
- @windows[name] = win
29
- win
30
- end
31
-
32
- # Get existing window
33
- def get_window(name)
34
- @windows[name]
35
- end
36
-
37
- # Destroy window
38
- def destroy_window(name)
39
- @windows[name]&.close
40
- @windows.delete(name)
41
- end
42
-
43
- # Refresh all windows efficiently
26
+ # Flush buffered drawing to the screen.
44
27
  def refresh_all
45
28
  @stdscr.noutrefresh
46
- @windows.values.each(&:noutrefresh)
47
29
  ::Curses.doupdate
48
30
  end
49
-
50
- def clear_all
51
- @stdscr.clear
52
- @windows.values.each(&:clear)
53
- end
54
31
  end
55
32
  end
data/lib/potty.rb CHANGED
@@ -4,6 +4,9 @@ require_relative 'potty/version'
4
4
  require_relative 'potty/keys'
5
5
  require_relative 'potty/events'
6
6
  require_relative 'potty/style'
7
+ require_relative 'potty/line_editor'
8
+ require_relative 'potty/ansi'
9
+ require_relative 'potty/input/decoder'
7
10
  require_relative 'potty/application'
8
11
  require_relative 'potty/theme'
9
12
  require_relative 'potty/layout'
@@ -30,6 +33,8 @@ require_relative 'potty/widgets/spinner'
30
33
  require_relative 'potty/sprite'
31
34
  require_relative 'potty/animator'
32
35
  require_relative 'potty/sprites/sample'
36
+ # Mouth's prompt views subclass Potty::View, so load it after the framework.
37
+ require_relative 'potty/mouth'
33
38
 
34
39
  module Potty
35
40
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: potty
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - TwilightCoders
@@ -43,6 +43,7 @@ email:
43
43
  - info@twilightcoders.net
44
44
  executables:
45
45
  - potty_demo
46
+ - potty_inline_demo
46
47
  extensions: []
47
48
  extra_rdoc_files: []
48
49
  files:
@@ -50,14 +51,19 @@ files:
50
51
  - LICENSE.txt
51
52
  - README.md
52
53
  - bin/potty_demo
54
+ - bin/potty_inline_demo
53
55
  - examples/test_view.rb
54
56
  - lib/potty.rb
55
57
  - lib/potty/animator.rb
58
+ - lib/potty/ansi.rb
56
59
  - lib/potty/application.rb
57
60
  - lib/potty/border.rb
58
61
  - lib/potty/events.rb
62
+ - lib/potty/input/decoder.rb
59
63
  - lib/potty/keys.rb
60
64
  - lib/potty/layout.rb
65
+ - lib/potty/line_editor.rb
66
+ - lib/potty/mouth.rb
61
67
  - lib/potty/sprite.rb
62
68
  - lib/potty/sprites/sample.rb
63
69
  - lib/potty/style.rb