interphase 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []