interphase 1.0.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 601ea62e61b89deb55c6ff518e6d8340dc6c6c73
4
+ data.tar.gz: 2f3c770ff1b03e94ed2f54b9ccb2bf804d7215a4
5
+ SHA512:
6
+ metadata.gz: 2feefd55a3a5186c6a091479faed94f32fc313480f47ed50ccfd1239e400a57e8ef78e750243bbf458ffe90e92b6d89b4330e854d9db951506652c1382e476f9
7
+ data.tar.gz: bb4b30b3de6fdf7d45b3e30a383ffad570f6862f0a66d1d0cf133e761ae6f380b6faf9fd47aed4bb1f467254d7b6dff8e5dbfab3ed6b1e09fcba63eb3b7d3e69
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'interphase/helpers/observable'
4
+
5
+ require 'interphase/widgets/basic_widgets'
6
+ require 'interphase/widgets/window'
7
+ require 'interphase/widgets/label'
8
+ require 'interphase/widgets/box'
9
+ require 'interphase/widgets/status_bar'
10
+ require 'interphase/widgets/list_view'
11
+ require 'interphase/widgets/simple_list_view'
12
+ require 'interphase/widgets/fixed'
13
+ require 'interphase/widgets/dialog'
14
+ require 'interphase/widgets/button'
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interphase
4
+ module Helpers
5
+ # An object wrapper which can invoke a method whenever the wrapped object
6
+ # changes.
7
+ # Unlike most classes, this class inherits from `BasicObject`, which has
8
+ # absolutely no methods whatsoever. This means that everything, even
9
+ # +inspect+ and +class+, fall into +Observable#method_missing+.
10
+ class Observable < BasicObject
11
+ # Wrap an object in a new instance of +Observable+.
12
+ # Takes a block which executes upon the object changing. This block is
13
+ # passed +object+ as a paremeter.
14
+ # +object+:: The object to wrap.
15
+ def initialize(object, &block)
16
+ # :: is required because we inherit BasicObject, not Object
17
+ ::Kernal.raise ::ArgumentError, 'Requires a block' if block.nil?
18
+
19
+ @object = object
20
+ @on_change = block
21
+ end
22
+
23
+ # TODO responds_to?
24
+ def method_missing(name, *args, &block)
25
+ if @object.respond_to?(name)
26
+ before_hash = @object.hash
27
+ ret_val = @object.send(name, *args, &block)
28
+ after_hash = @object.hash
29
+
30
+ @on_change.call(@object) if before_hash != after_hash
31
+
32
+ ret_val
33
+ else
34
+ super
35
+ end
36
+ end
37
+
38
+ def respond_to_missing?(*)
39
+ true
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'gtk2'
4
+
5
+ module Interphase
6
+ # A basic GTK widget wrapper.
7
+ class Widget
8
+ attr_accessor :gtk_instance, :parent, :name
9
+
10
+ # Creates a new widget.
11
+ # +gtk_instance+:: The GTK widget instance this is wrapping.
12
+ # +name:+:: This widgets name, allowing it to be referred to after being
13
+ # created.
14
+ def initialize(gtk_instance, **options, &block)
15
+ @gtk_instance = gtk_instance
16
+ @parent = nil
17
+ @name = options[:name]
18
+
19
+ instance_eval(&block) if block_given?
20
+ end
21
+
22
+ # Requests that this widget is resized. Note that this is a method, rather
23
+ # than a 'size=' setter, because the request is not guaranteed, and indeed
24
+ # in many cases will not. Only some containers allow their child widgets
25
+ # to be resized.
26
+ def size(width, height)
27
+ gtk_instance.set_size_request(width, height)
28
+ end
29
+
30
+ # Shows this widget.
31
+ def show
32
+ gtk_instance.show
33
+ end
34
+
35
+ # Associates a block with a signal. The block is invoked whenever the
36
+ # signal occurs.
37
+ # +name+:: The name of the signal.
38
+ def on(name, &block)
39
+ gtk_instance.signal_connect(name, &block)
40
+ end
41
+
42
+ # Destroy this widget.
43
+ def destroy
44
+ gtk_instance.destroy
45
+ end
46
+
47
+ # Respond to lookups by name.
48
+ # TODO IMPLEMENT RESPONDS_TO
49
+ def method_missing(requested, *args, &block)
50
+ # If any arguments or a block have been given, then this isn't an attr
51
+ if !args.empty? || block_given?
52
+ super
53
+ return
54
+ end
55
+
56
+ return self if requested.to_s == name
57
+
58
+ super
59
+ end
60
+
61
+ def respond_to_missing?
62
+ true
63
+ end
64
+ end
65
+
66
+ # A widget which may contain other widgets.
67
+ class Container < Widget
68
+ attr_accessor :children
69
+
70
+ # Add a widget as a child of this one.
71
+ # Accepts a block which is executed on the child.
72
+ # +child+:: The new child widget.
73
+ # +should_add+:: (Optional) Whether to actually add the element, or just to
74
+ # register it as added by adding it to +children+. You
75
+ # probably shouldn't change this.
76
+ def add(child, should_add = true, &block)
77
+ child.instance_eval(&block) if block_given?
78
+
79
+ raise 'Widget already has a parent' unless child.parent.nil?
80
+
81
+ gtk_instance.add(child.gtk_instance) if should_add
82
+ child.parent = self
83
+
84
+ # Ensure a children array exists, and add the new child to it
85
+ @children ||= []
86
+ children << child
87
+ end
88
+
89
+ # Show this widget and all of its children.
90
+ def show_all
91
+ gtk_instance.show_all
92
+ end
93
+
94
+ # Allows child named widgets to be looked up like an attribute.
95
+ # TODO IMPLEMENT RESPONDS_TO
96
+ def method_missing(requested, *args, &block)
97
+ (children || []).each do |child|
98
+ # An exception simply means that wasn't the child we were looking for
99
+ begin
100
+ return child.send(requested)
101
+ rescue StandardError
102
+ next
103
+ end
104
+ end
105
+
106
+ super
107
+ end
108
+
109
+ def respond_to_missing?(*)
110
+ true
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'gtk2'
4
+
5
+ module Interphase
6
+ # A simple item container. You should use +VBox+ or +HBox+ rather than this.
7
+ class Box < Container
8
+ PACK_START = 0
9
+ PACK_END = 1
10
+
11
+ # Add a widget to the box, after all others added at the same reference.
12
+ # Accepts a block which is executed on the child.
13
+ # +child+:: The new child widget.
14
+ # +expand+:: (Optional) Allocate extra space to this widget, default +true+.
15
+ # +fill+:: (Optional) Allocate to the full width/height of the box, default
16
+ # +true+.
17
+ # +padding+:: (Optional) Any padding to allocate to this widget, default 0.
18
+ # +ref+:: (Optional) The reference at which to add the widget. Either
19
+ # +PACK_START+ (default) or +PACK_END+.
20
+ def add(child, expand = true, fill = true, padding = 0, ref = PACK_START, &block)
21
+ super(child, false, &block)
22
+
23
+ if ref == PACK_START
24
+ gtk_instance.pack_start(child.gtk_instance, expand, fill, padding)
25
+ elsif ref == PACK_END
26
+ gtk_instance.pack_end(child.gtk_instance, expand, fill, padding)
27
+ else
28
+ raise 'ref should be either PACK_START or PACK_END'
29
+ end
30
+ end
31
+ end
32
+
33
+ # A vertical item container.
34
+ class VBox < Box
35
+ # Creates a new vertical box.
36
+ def initialize(**options, &block)
37
+ super(Gtk::VBox.new, options, &block)
38
+ end
39
+ end
40
+
41
+ # A horizontal item container.
42
+ class HBox < Box
43
+ # Creates a new horizontal box.
44
+ def initialize(**options, &block)
45
+ super(Gtk::HBox.new, options, &block)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'gtk2'
4
+
5
+ module Interphase
6
+ # A button which can perform an action upon being clicked.
7
+ class Button < Widget
8
+ # Create a new button.
9
+ # +label+:: The text to display on the button.
10
+ def initialize(label = '', **options, &block)
11
+ super(Gtk::Button.new(label), **options, &block)
12
+ end
13
+
14
+ # Get the button's label text.
15
+ def label
16
+ gtk_instance.label
17
+ end
18
+
19
+ # Set the button's label text.
20
+ def label=(value)
21
+ gtk_instance.label = value
22
+ end
23
+
24
+ # Register a block to execute upon clicking the button.
25
+ def on_click(&block)
26
+ on('clicked', &block)
27
+ end
28
+
29
+ # Clicks the button, executing any blocks added using +#on_click+.
30
+ def click
31
+ gtk_instance.clicked
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'gtk2'
4
+
5
+ module Interphase
6
+ # A base class representing a simple, blank dialog box.
7
+ class Dialog < Widget
8
+ # Construct a new blank dialog box.
9
+ def initialize(instance = nil, **options, &block)
10
+ super(instance || Gtk::Dialog.new, **options, &block)
11
+
12
+ options['buttons']&.map { |btn| add_button(*btn) }
13
+ end
14
+
15
+ # Converts an +#add_button+ +action+ symbol to the GTK RESPONSE integer
16
+ # which it represents.
17
+ # +action+:: The action symbol to convert. Should be one of the valid
18
+ # values of +action+ to +#add_button+.
19
+ def action_to_integer(action)
20
+ valid_actions =
21
+ %i[none reject accept delete ok cancel close yes no apply help]
22
+
23
+ throw ArgumentError, 'Invalid button action' \
24
+ unless valid_actions.include? action
25
+
26
+ # Map :delete to its GTK equivalent
27
+ action = :delete_event if action == :delete
28
+
29
+ Gtk::Dialog.const_get("RESPONSE_#{action.to_s.upcase}")
30
+ end
31
+
32
+ # Adds a button to the dialog box.
33
+ # +text+:: The text to display on the button.
34
+ # +action+:: The action which occurs when this button is clicked. Must be
35
+ # one of: +:none+, +:reject+, +:accept+, +:delete+, +:ok+,
36
+ # +:cancel+, +:close+, +:yes+, +:no+, +:apply+, +:help+.
37
+ def add_button(text, action)
38
+ gtk_instance.add_button(text, action_to_integer(action))
39
+ nil
40
+ end
41
+
42
+ # Run this dialog box. This call will block execution until the dialog is
43
+ # closed. Consider using +Window#in_background+ if blocking is not desired.
44
+ # You should usually call +destroy+ after this method, otherwise the dialog
45
+ # will remain even after selecting an option.
46
+ def run
47
+ gtk_instance.run
48
+ end
49
+ end
50
+
51
+ # A message dialog which displays text and some buttons.
52
+ class MessageDialog < Dialog
53
+ # Create a new message dialog.
54
+ # +message+:: The message which the dialog displays.
55
+ def initialize(message, **options, &block)
56
+ super(
57
+ Gtk::MessageDialog.new(
58
+ nil,
59
+ 0,
60
+ Gtk::MessageDialog::OTHER,
61
+ Gtk::MessageDialog::BUTTONS_NONE,
62
+ message
63
+ ),
64
+ options, &block
65
+ )
66
+ end
67
+
68
+ # A helper method which creates a new +MessageDialog+, adds an OK button,
69
+ # displays it, blocks until 'OK' is clicked, then destroys it.
70
+ # +message+:: The message which the dialog displays.
71
+ def self.show(message)
72
+ dialog = MessageDialog.new(message)
73
+ dialog.add_button('OK', :ok)
74
+ dialog.run
75
+ dialog.destroy
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'gtk2'
4
+
5
+ module Interphase
6
+ # A container in which items can be placed in a specific x/y location.
7
+ class Fixed < Container
8
+ # Construct a new fixed container.
9
+ def initialize(**options, &block)
10
+ super(Gtk::Fixed.new, **options, &block)
11
+ end
12
+
13
+ # Add a child widget to this +Fixed+ at a location.
14
+ # +child+:: The new child widget.
15
+ # +x_pos+:: The x position.
16
+ # +y_pos+:: The y position.
17
+ def add(child, x_pos, y_pos, &block)
18
+ gtk_instance.put(child.gtk_instance, x_pos, y_pos)
19
+
20
+ super(child, false, &block)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'gtk2'
4
+
5
+ module Interphase
6
+ # A text label.
7
+ class Label < Widget
8
+ # Creates a new label.
9
+ def initialize(text = '', **options, &block)
10
+ super(Gtk::Label.new(text), options, &block)
11
+ end
12
+
13
+ # Set the text in this label.
14
+ def text=(value)
15
+ gtk_instance.text = value
16
+ end
17
+
18
+ # Retrieve the label's text.
19
+ def text
20
+ gtk_instance.text
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'gtk2'
4
+ require 'interphase/helpers/observable'
5
+
6
+ module Interphase
7
+ # A list view which can be populated with objects.
8
+ class ListView < Widget
9
+ attr_reader :rows
10
+
11
+ # Create a new list view.
12
+ # +columns+:: The columns which this list view has, as an +Array+ of
13
+ # +String+ objects.
14
+ def initialize(columns, **options, &block)
15
+ @store = Gtk::ListStore.new(*[String] * columns.length)
16
+
17
+ super(Gtk::TreeView.new(@store), options, &block)
18
+
19
+ # Init columns
20
+ columns.each_with_index do |col, index|
21
+ renderer = Gtk::CellRendererText.new
22
+ new_col = Gtk::TreeViewColumn.new(col[0], renderer, text: index)
23
+ gtk_instance.append_column(new_col)
24
+ end
25
+
26
+ @rows = Interphase::Helpers::Observable.new([]) { refresh_rows }
27
+
28
+ refresh_rows
29
+ end
30
+
31
+ # Refreshes the contents of the list view according to its rows. This is
32
+ # called automatically upon mutating #rows.
33
+ def refresh_rows
34
+ @store.clear
35
+
36
+ # Insert the rows
37
+ @rows.each do |data_row|
38
+ store_row = @store.append
39
+
40
+ # Basically a memcpy
41
+ data_row.each_with_index do |item, index|
42
+ store_row[index] = item
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'gtk2'
4
+ require 'interphase/helpers/observable'
5
+
6
+ module Interphase
7
+ # A simple list view which displays an +Array+.
8
+ class SimpleListView < ListView
9
+ attr_reader :items
10
+
11
+ def initialize(items = [], **options, &block)
12
+ super(['Column'], options, &block)
13
+
14
+ gtk_instance.headers_visible = false
15
+
16
+ @items = Interphase::Helpers::Observable.new(items) do
17
+ refresh_items
18
+ end
19
+
20
+ refresh_items
21
+ end
22
+
23
+ # Copies #items into #rows, where each item is one row.
24
+ def refresh_items
25
+ items.each do |item|
26
+ rows << [item]
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'gtk2'
4
+
5
+ module Interphase
6
+ # Provides a simple status bar which can display a single message.
7
+ class SimpleStatusBar < Widget
8
+ attr_reader :text
9
+
10
+ # Create a new simple status bar.
11
+ def initialize(**options, &block)
12
+ super(Gtk::Statusbar.new, options, &block)
13
+ end
14
+
15
+ # Sets the text displayed in the status bar.
16
+ def text=(value)
17
+ # Value is frozen to prevent modification without updating the text on the
18
+ # widget (i.e. mutation must be through this method)
19
+ @text = value.clone.freeze
20
+
21
+ begin
22
+ gtk_instance.pop(1)
23
+ rescue StandardError # rubocop:disable Lint/HandleExceptions
24
+ # It doesn't matter; just means that there was no text previously
25
+ end
26
+
27
+ gtk_instance.push(1, text)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'gtk2'
4
+
5
+ module Interphase
6
+ # An application window.
7
+ class Window < Container
8
+ # Creates a new window.
9
+ def initialize(title, **options, &block)
10
+ super(Gtk::Window.new(title), options, &block)
11
+ end
12
+
13
+ # The given block will trigger when this window is closed.
14
+ def on_delete(&block)
15
+ on('delete-event', &block)
16
+ end
17
+
18
+ # Registers an #on_delete block which calls #quit.
19
+ def quit_on_delete!
20
+ on_delete do
21
+ quit
22
+ end
23
+ end
24
+
25
+ # Runs the entire Interphase application.
26
+ def run
27
+ Gtk.main
28
+ end
29
+
30
+ # Quits the entire Interphase application, killing all its threads.
31
+ def quit
32
+ @threads&.each(&:kill)
33
+ Gtk.main_quit
34
+ end
35
+
36
+ # Force-quits the entire Interphase application, including the Ruby Kernel.
37
+ # If your GUI quits but your terminal stays occupied after #quit, this will
38
+ # probably solve that issue.
39
+ # Having to use this is usually the sign of a badly-written program!
40
+ def quit!
41
+ @threads&.each(&:kill)
42
+ Gtk.main_quit
43
+ exit!
44
+ end
45
+
46
+ # Binds a block to run as a +Thread+; it begins executing immediately.
47
+ # Destroying the window kills all threads.
48
+ # TOOD DOES IT?
49
+ def in_background(&block)
50
+ @threads ||= []
51
+ thread = Thread.new(&block)
52
+ thread.abort_on_exception = true
53
+ @threads << thread
54
+ end
55
+ end
56
+ end
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: interphase
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Aaron Christiansen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-03-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: gtk2
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.2'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 3.2.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '3.2'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 3.2.0
33
+ description:
34
+ email: aaronc20000@gmail.com
35
+ executables: []
36
+ extensions: []
37
+ extra_rdoc_files: []
38
+ files:
39
+ - lib/interphase.rb
40
+ - lib/interphase/helpers/observable.rb
41
+ - lib/interphase/widgets/basic_widgets.rb
42
+ - lib/interphase/widgets/box.rb
43
+ - lib/interphase/widgets/button.rb
44
+ - lib/interphase/widgets/dialog.rb
45
+ - lib/interphase/widgets/fixed.rb
46
+ - lib/interphase/widgets/label.rb
47
+ - lib/interphase/widgets/list_view.rb
48
+ - lib/interphase/widgets/simple_list_view.rb
49
+ - lib/interphase/widgets/status_bar.rb
50
+ - lib/interphase/widgets/window.rb
51
+ homepage:
52
+ licenses:
53
+ - MIT
54
+ metadata: {}
55
+ post_install_message:
56
+ rdoc_options: []
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ requirements: []
70
+ rubyforge_project:
71
+ rubygems_version: 2.6.14
72
+ signing_key:
73
+ specification_version: 4
74
+ summary: A powerful, easy-to-use, native-looking GUI library
75
+ test_files: []