less_curse 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,106 @@
1
+ # LessCurse
2
+
3
+ Simple terminal-based ncurses UIs.
4
+
5
+ LessCurse is a work-in-progress, but it's unclear how much work and how much progress will happen/is needed.
6
+
7
+ It will probably just be developed enough to serve as a dead-simple TUI for the trackt time-tracking system.
8
+
9
+ I'm happy about bug reports and ideas, but chances are high that I cannot react in the way I wish others would do.
10
+
11
+ ## License
12
+
13
+ LessCurse is released under the GPL, Version 3 or any later version and Copyright 2016 by Felix Wolfsteller.
14
+
15
+ ## Installation
16
+
17
+ Add this line to your application's Gemfile:
18
+
19
+ ```ruby
20
+ gem 'lesscurse'
21
+ ```
22
+
23
+ And then execute:
24
+
25
+ $ bundle
26
+
27
+ Or install it yourself as:
28
+
29
+ $ gem install lesscurse
30
+
31
+ ## Usage
32
+
33
+ See the examples folder for now. Note that executing the examples creates a log file next to it (e.g. examples/list.rb.log).
34
+ It boils down to initialize a Screen, populate it with Widgets and create lambdas to react to events (like a list item was selected) or key-presses.
35
+
36
+ ## Concepts
37
+
38
+ ### Screen
39
+
40
+ There is only one screen and it will fill up your terminal. Access it via `LessCurse::screen` (or `LessCurse.screen`).
41
+
42
+ The screen can and should be populated by widgets. You cannot decide on exact positioning of widgets, as a *tiling*-approach is used. By default, the screen will split vertically and each widget will take up an equal amount of space. However, slightly more complex `Grid`-layouts are possible (see below).
43
+
44
+ Always one widget is focused.
45
+
46
+ The screen can handle global keyboard input (shortcuts, in LessCurse#actions). Currently, the TAB key is used to switch the focus of widgets. CTRL_Q will quit the application.
47
+
48
+ ### The Grid
49
+
50
+ A Grid can be used if the screen is to be tiled not only vertically, but also horizontally. Each row will take up an equal amount of space, where each widget in a row shares the space within that row equally.
51
+
52
+ ### Widgets
53
+
54
+ All (four... :)) widgets inherit from `LessCurse::Widgets::Base` and provide following methods:
55
+
56
+ - `new(title: "Shows on top", data: "Shows somewhere")` [creates instance]
57
+ - `set_default_actions` [populates the @action map (keys to lambdas)]
58
+ - `draw` [(re)draws the widget]
59
+ - `handle_input(key)` [deals with input, that will be handed on from main module if focused]
60
+ has to return true if key press was dealt with
61
+
62
+ The default look of a widget has a box drawn around it, with an optional title at the top.
63
+
64
+ #### List
65
+
66
+ Shows a list where a single item can be selected with the UP and DOWN array keys.
67
+ The list is thought to be able to gobble up **Lists of Objects** which can be selected and are then represented as `List.selected_data`.
68
+ The text which is shown per Object defaults to `object.to_s` but can be overridden by passing a lambda into `List.display_func`.
69
+
70
+ #### TextView
71
+
72
+ Shows text. Doesnt even scroll yet.
73
+
74
+ #### TextArea
75
+
76
+ Allows creepy text input.
77
+
78
+ #### Buttons
79
+
80
+ Buttons do not look very much like buttons yet, but can execute a lambda on keypress (typically `RETURN`) when focused.
81
+
82
+ ### Popups
83
+
84
+
85
+ ## Development
86
+
87
+ After checking out the repo, run `bundle` to install dependencies. You can also run `bundle console` for an interactive prompt that will allow you to experiment.
88
+
89
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
90
+
91
+ ### Design goals and decisions
92
+
93
+ LessCurse aims to solve a specific problem and the library user (me) should be able to hack a UI with ease and without caring too much about configuration.
94
+
95
+ Thus, LessCurse is **very** oppinionated.
96
+
97
+ - No complex layouts, only screen-tiling.
98
+ - Few options.
99
+ - No way to get lost in visual design decisions.
100
+
101
+ Widgets are for now not directly aware of the underlying (ncurses) window, this is a design decision workaround and yet on purpose.
102
+
103
+ ## Contributing
104
+
105
+ Bug reports and pull requests are welcome on GitHub at https://github.com/econya/less_curse.
106
+
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "lesscurse"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env ruby
2
+ # Released under the GPLv3+, Copyright 2016 Felix Wolfsteller
3
+
4
+ require 'less_curse'
5
+
6
+ LessCurse.log_to! $PROGRAM_NAME + ".log"
7
+
8
+ def list_ui
9
+ simple_list = [1, 2, 3, 4, 5, 6, 7]
10
+
11
+ list_ui = LessCurse::Widgets::List.new
12
+ list_ui.data = simple_list
13
+ list_ui.on_select = LessCurse::Actions::QUIT
14
+ list_ui
15
+ end
16
+
17
+ def hash_ui
18
+ simple_hash = {rainbows: "fabolous",
19
+ unicorns: "magnificient",
20
+ sunrise: "fantastic",
21
+ sunset: "marvellous"}
22
+ LessCurse::Widgets::List.new data: simple_hash, title: "Hash keys"
23
+ end
24
+
25
+ def textview_ui
26
+ LessCurse::Widgets::TextView.new title: 'TextView',
27
+ data: "Showing Text Data\nSecond Line of Text Data"
28
+ end
29
+
30
+ begin
31
+ # Display list items
32
+ LessCurse.screen.add list_ui
33
+
34
+ # Display hash items
35
+ LessCurse.screen.add hash_ui
36
+
37
+ # Display important information
38
+ LessCurse.screen.add textview_ui
39
+
40
+ # Display it, really
41
+ LessCurse.show_screen
42
+
43
+ # Deal with keyboard input and keep us living
44
+ LessCurse.enter_loop!
45
+
46
+ # Try to debug
47
+ LessCurse.screen.windows.each do |widget, window|
48
+ puts "#{widget} -> #{window}"
49
+ end
50
+ ensure
51
+ LessCurse.close_screen
52
+ end
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env ruby
2
+ # Released under the GPLv3+, Copyright 2016 Felix Wolfsteller
3
+
4
+ require 'less_curse'
5
+
6
+ viewer = LessCurse::Widgets::TextView.new title: 'File Content',
7
+ data: 'Use TAB to change focus\n'\
8
+ 'UP/DOWN to select file\n'\
9
+ 'CTRL+Q to quit.'
10
+
11
+ file_list_ui = LessCurse::Widgets::List.new title: 'Files:',
12
+ data: Dir['*']
13
+ file_list_ui.on_select = lambda do |selected_file|
14
+ viewer.title = selected_file
15
+ if File.file? selected_file
16
+ viewer.data = File.new(selected_file).readlines
17
+ else
18
+ viewer.data = "[Probably a directory]"
19
+ end
20
+ end
21
+
22
+ begin
23
+ # Display file list
24
+ LessCurse.screen.add file_list_ui
25
+
26
+ # Display file content (once file selected)
27
+ LessCurse.screen.add viewer
28
+
29
+ # Show off with header and footer
30
+ LessCurse.screen.header = "-- ** filebrowser ** --"
31
+ LessCurse.screen.footer = "[CTRL_Q to quit]"
32
+
33
+ # Display it, really
34
+ LessCurse.show_screen
35
+
36
+ # Deal with keyboard input and keep us living
37
+ LessCurse.enter_loop!
38
+ ensure
39
+ LessCurse.close_screen
40
+ end
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env ruby
2
+ # Released under the GPLv3+, Copyright 2016 Felix Wolfsteller
3
+
4
+ require 'less_curse'
5
+
6
+ LessCurse.log_to! $PROGRAM_NAME + ".log"
7
+
8
+ def list_ui
9
+ simple_list = [1, 2, 3, 4, 5, 6, 7]
10
+
11
+ list_ui = LessCurse::Widgets::List.new
12
+ list_ui.data = simple_list
13
+ list_ui.on_select = LessCurse::Actions::QUIT
14
+ list_ui
15
+ end
16
+
17
+ def hash_ui
18
+ simple_hash = {rainbows: "fabolous",
19
+ unicorns: "magnificient",
20
+ sunrise: "fantastic",
21
+ sunset: "marvellous"}
22
+ LessCurse::Widgets::List.new data: simple_hash, title: "Hash keys"
23
+ end
24
+
25
+ def textview_ui
26
+ LessCurse::Widgets::TextView.new title: 'TextView',
27
+ data: "Showing Text Data\nSecond Line of Text Data"
28
+ end
29
+
30
+ begin
31
+ grid = LessCurse::Grid.new [[list_ui, hash_ui],
32
+ [textview_ui]]
33
+ # Set grid
34
+ LessCurse.screen.add grid
35
+
36
+ # Display it, really
37
+ LessCurse.show_screen
38
+
39
+ # Deal with keyboard input and keep us living
40
+ LessCurse.enter_loop!
41
+
42
+ # Try to debug
43
+ LessCurse.screen.windows.each do |widget, window|
44
+ puts "#{widget} -> #{window}"
45
+ end
46
+ ensure
47
+ LessCurse.close_screen
48
+ end
49
+
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env ruby
2
+ # Released under the GPLv3+, Copyright 2016 Felix Wolfsteller
3
+
4
+ require 'less_curse'
5
+
6
+ LessCurse.log_to! $PROGRAM_NAME + ".log"
7
+
8
+ begin
9
+ # Display list items
10
+ simple_list = [1, 2, 3, 4, 5, 6, 7]
11
+
12
+ list_ui = LessCurse::Widgets::List.new
13
+ list_ui.data = simple_list
14
+ list_ui.on_select = LessCurse::Actions::QUIT
15
+
16
+ LessCurse.screen.add list_ui
17
+ LessCurse.show_screen
18
+ LessCurse.enter_loop!
19
+
20
+ # Display hash items
21
+ simple_hash = {rainbows: "fabolous",
22
+ unicorns: "magnificient",
23
+ sunrise: "fantastic",
24
+ sunset: "marvellous"}
25
+
26
+ list_ui.data = simple_hash
27
+ list_ui.title = "Hash keys"
28
+
29
+ textview_ui = LessCurse::Widgets::TextView.new title: 'TextView',
30
+ data: "Showing Text Data\nSecond Line of Text Data"
31
+
32
+ list_ui.on_select = lambda do |selected_item|
33
+ textview_ui.data = "Selected: #{selected_item}"
34
+ end
35
+
36
+ LessCurse.screen.add textview_ui
37
+ LessCurse.show_screen
38
+ LessCurse.enter_loop!
39
+
40
+ LessCurse.screen.windows.each do |widget, window|
41
+ puts "#{widget} -> #{window}"
42
+ end
43
+
44
+ ensure
45
+ LessCurse.close_screen
46
+ end
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+ # Released under the GPLv3+, Copyright 2016 Felix Wolfsteller
3
+
4
+ require 'less_curse'
5
+
6
+ LessCurse.log_to! $PROGRAM_NAME + ".log"
7
+
8
+ begin
9
+ # Display hash items
10
+ list_ui = LessCurse::Widgets::List.new
11
+ simple_hash = {rainbows: "fabolous",
12
+ unicorns: "magnificient",
13
+ sunrise: "fantastic",
14
+ sunset: "marvellous"}
15
+
16
+ list_ui.data = simple_hash
17
+ list_ui.title = "Hash keys"
18
+
19
+ list_ui.display_func = lambda {|i| i[0].to_s}
20
+
21
+ LessCurse.screen.add list_ui
22
+ LessCurse.show_screen
23
+ LessCurse.enter_loop!
24
+ ensure
25
+ LessCurse.close_screen
26
+ end
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+ # Released under the GPLv3+, Copyright 2016 Felix Wolfsteller
3
+
4
+ require 'less_curse'
5
+
6
+ LessCurse.log_to! $PROGRAM_NAME + ".log"
7
+
8
+ begin
9
+ # Display list items
10
+ simple_list = (1..200).to_a
11
+
12
+ list_ui = LessCurse::Widgets::List.new
13
+ list_ui.data = simple_list
14
+ list_ui.on_select = LessCurse::Actions::QUIT
15
+
16
+ LessCurse.screen.add list_ui
17
+ LessCurse.show_screen
18
+ LessCurse.enter_loop!
19
+ ensure
20
+ LessCurse.close_screen
21
+ end
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env ruby
2
+ # Released under the GPLv3+, Copyright 2016 Felix Wolfsteller
3
+
4
+ require 'less_curse'
5
+
6
+ LessCurse.log_to! $PROGRAM_NAME + ".log"
7
+
8
+ begin
9
+ # Initialize Widgets
10
+ list_ui = LessCurse::Widgets::List.new
11
+ list_ui.data = [1, 2, 3, 4, 5, 6, 7]
12
+
13
+ button = LessCurse::Widgets::Button.new title: "Square that number in a popup"
14
+ button.on_press = lambda do |b|
15
+ selection_square = list_ui.selected_data * list_ui.selected_data
16
+ LessCurse.screen.show_popup :info, "#{selection_square}!"
17
+ end
18
+
19
+ # Display list items
20
+ LessCurse.screen.add list_ui
21
+
22
+ # Display button
23
+ LessCurse.screen.add button
24
+
25
+ # Display it, really
26
+ LessCurse.show_screen
27
+
28
+ # Deal with keyboard input and keep us living
29
+ LessCurse.enter_loop!
30
+ ensure
31
+ LessCurse.close_screen
32
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'less_curse/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "less_curse"
8
+ spec.version = LessCurse::VERSION
9
+ spec.authors = ["Felix Wolfsteller"]
10
+ spec.email = ["felix.wolfsteller@gmail.com"]
11
+
12
+ spec.summary = %q{ncurses abstraction layer for terminal-based applications.}
13
+ spec.description = %q{LessCurse is a ncurses abstraction layer for terminal-based user interfaces.}
14
+ spec.homepage = "https://github.com/econya/less_curse"
15
+ spec.license = "GPLv3+"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency "ffi-ncurses", '~> 0.4'
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.11"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ end
@@ -0,0 +1,76 @@
1
+ require 'logger'
2
+ require 'ffi-ncurses'
3
+
4
+ require 'less_curse/version'
5
+ require 'less_curse/geometry'
6
+ require 'less_curse/screen'
7
+ require 'less_curse/grid'
8
+ require 'less_curse/actions'
9
+ require 'less_curse/null_logger'
10
+
11
+ require "less_curse/renderer"
12
+
13
+ require "less_curse/widgets"
14
+
15
+ # The LessCurse module which gives you static access to your "Screen" and
16
+ # all the UI glory within it.
17
+ module LessCurse
18
+ @@actions = {
19
+ FFI::NCurses::KEY_TAB => lambda { screen.focus_next },
20
+ FFI::NCurses::KEY_BTAB => lambda { screen.focus_previous }
21
+ }
22
+
23
+ def self.screen
24
+ @@screen ||= Screen.new
25
+ end
26
+
27
+ def self.show_screen
28
+ @@screen.show
29
+ end
30
+
31
+ def self.close_screen
32
+ FFI::NCurses.endwin
33
+ # flushinp, delwin?
34
+ end
35
+
36
+ def self.window rectangle
37
+ FFI::NCurses.newwin rectangle.size.height, rectangle.size.width,
38
+ rectangle.position.y, rectangle.position.x
39
+ end
40
+
41
+ def self.enter_loop!
42
+ loop do
43
+ break if !handle_input
44
+ screen.repaint
45
+ end
46
+ end
47
+
48
+ # Read from keyboard, handle keypress oneself or dispatch
49
+ # to focused widget.
50
+ def self.handle_input
51
+ key = FFI::NCurses.wgetch FFI::NCurses::stdscr
52
+ debug_msg "key press: #{key} / keyname: #{FFI::NCurses::keyname key} / #{FFI::NCurses::KEY_CTRL_Q}"
53
+
54
+ # That will let us break out of the loop
55
+ return false if key == FFI::NCurses::KEY_CTRL_Q
56
+
57
+ if @@actions[key]
58
+ # Global actions first
59
+ @@actions[key].call
60
+ else
61
+ screen.focused_widget.handle_input key
62
+ end
63
+ end
64
+
65
+ def self.debug_msg msg
66
+ logger.debug msg
67
+ end
68
+
69
+ def self.logger
70
+ @@logger ||= LessCurse::NullLogger.new
71
+ end
72
+
73
+ def self.log_to! file
74
+ @@logger = Logger.new(file)
75
+ end
76
+ end