tkwrapper 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6cf6fe96ebab8380076797dfe3f4ed9747d3f3709cc8d58e1bf1c012d28af572
4
+ data.tar.gz: b0956e96f04f7daf0b6d6f09317e7b2e73d010e866b57f678fa2fd7ba658fd16
5
+ SHA512:
6
+ metadata.gz: 848528e93f0dabed494f7e234789edc0108b4460b11964bc661c9457aeabcf2a022565461d4ae3d566de853ad2e96ec06b4112c2abf9f1520912c999675c9bd1
7
+ data.tar.gz: 613ec39818df4edb5714f0eac7f3aac73c7b403ed0a5d2371103a2e5419ec0c8bc763a2ec068b12538e2476cd29f1e0f49622af3cbcdfa7c7a734edc783c5c62
@@ -0,0 +1,49 @@
1
+ require 'tk'
2
+ require 'tkextlib/tile'
3
+
4
+ module TkExtensions
5
+ # allow a Tk widget to have multiple bindings for the same event
6
+ module MultiBind
7
+ def bind(event, &callback)
8
+ @bindings ||= {}
9
+
10
+ unless @bindings[event]
11
+ @bindings[event] = []
12
+ super(event) { execute_all_bindings(event) }
13
+ end
14
+
15
+ @bindings[event].push(callback)
16
+ end
17
+
18
+ def execute_all_bindings(event)
19
+ @bindings[event].each(&:call)
20
+ end
21
+ end
22
+
23
+ module TkWidgets
24
+ # from several Tk widgets create subclasses, which include MultiBind
25
+ # the reason why we loop over strings and not the classes themselve is, that
26
+ # the class names may be aliases (e.g. Tk::Tile::Entry is an alias for
27
+ # Tk::Tile::TEntry)
28
+ tk_class_names = [
29
+ 'TkRoot', # becomes TkExtensions::TkRoot
30
+ 'TkText', # becomes TkExtensions::TkText
31
+ 'TkMenu', # becomes TkExtensions::TkMenu
32
+ 'Tk::Tile::Entry', # becomes TkExtensions::Entry
33
+ 'Tk::Tile::Frame', # becomes TkExtensions::Frame
34
+ 'Tk::Tile::Label' # becomes TkExtensions::Label
35
+ ]
36
+ tk_class_names.each do |tk_class_name|
37
+ # extract last part of the name (e.g. Tk::Tile::Entry => Entry)
38
+ new_child_class_name = tk_class_name.match(/([^:]*)$/)[1]
39
+ # get the class from the class constant name
40
+ tk_class = const_get(tk_class_name)
41
+ # create a new child class of tk_class and have it include MultiBind, which
42
+ # overwrites the bind method of that class to allow for multiple bindings
43
+ new_child_class = Class.new(tk_class) { include(MultiBind) }
44
+ # make the new class known to the TkExtensions namespace to allow to use it
45
+ # by e.g. TkExtensions::Entry
46
+ const_set(new_child_class_name, new_child_class)
47
+ end
48
+ end
49
+ end
data/lib/tkwrapper.rb ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ LIB_DIR = __dir__
4
+
5
+ module TkWrapper end
6
+
7
+ require_relative 'widgets/widgets'
8
+
9
+ module TkWrapper
10
+ Widget = TkWrapper::Widgets::Base::Widget
11
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Util
4
+ def self.merge_recursive!(*hashes)
5
+ hashes[0].merge!(*hashes[1..]) do |_key, old, new|
6
+ if old.is_a?(Hash) && new.is_a?(Hash)
7
+ merge_recursive!(old, new)
8
+ else
9
+ new
10
+ end
11
+ end
12
+ end
13
+
14
+ def self.each_recursive(hash, &block)
15
+ hash.each do |key, value|
16
+ next block.call(hash, key, value) unless value.is_a?(Hash)
17
+
18
+ block.call(hash, key, each_recursive(value, &block))
19
+ end
20
+ end
21
+
22
+ def self.recursive_transform_key_value!(hash, &block)
23
+ hash.transform_keys! do |key|
24
+ value = hash[key]
25
+ next recursive_transform_key_value!(value, &block) if value.is_a?(Hash)
26
+
27
+ block.call(key, value)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ # wraps some code of Tk to make it more object oriented, provide new
4
+ # functionality and to ease usage
5
+ module TkWrapper
6
+ # used to be abled to define virtual methods in a class which is extended by
7
+ # this module
8
+ module VirtualMethods
9
+ # error thrown, when a virtual method is called
10
+ class VirtualMethodCalledError < RuntimeError
11
+ def initialize(name)
12
+ super("Virtual function '#{name}' called.")
13
+ end
14
+ end
15
+
16
+ def virtual(method)
17
+ define_method(method) { raise VirtualMethodCalledError, method }
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TkWrapper::Widgets::AutoResizeEntry < TkWrapper::Widgets::Entry
4
+ # auto resizes on user input, only works if in the grid geometry manager of tk
5
+ attr_accessor :min_width, :add_width
6
+
7
+ def initialize(config: {}, childs: [])
8
+ @min_width = config.delete(:min_width) || 0
9
+ @add_width = config.delete(:add_width) || 0
10
+ super(config: config, childs: childs)
11
+ end
12
+
13
+ def build(parent, configure: true)
14
+ super(parent, configure: configure)
15
+ parent.tk_widget.bind('Configure') { resize }
16
+ tk_widget.textvariable = TkVariable.new unless tk_widget.textvariable
17
+ tk_widget.textvariable.trace('write') { resize }
18
+ resize
19
+ end
20
+
21
+ def config_for_dummy_label
22
+ grid_info = TkGrid.info(tk_widget)
23
+ { config: { grid: {
24
+ row: grid_info['row'],
25
+ column: grid_info['column'],
26
+ columnspan: grid_info['columnspan'],
27
+ sticky: 'nw'
28
+ } } }
29
+ end
30
+
31
+ def create_dummy_label_with_same_size(&block)
32
+ label = Label.new(**config_for_dummy_label)
33
+ label.build(@parent)
34
+ label.tk_widget.text = tk_widget.textvariable.value
35
+ label.tk_widget.lower
36
+ result = block.call(label)
37
+ label.tk_widget.destroy
38
+ result
39
+ end
40
+
41
+ def text_width_in_pixel
42
+ create_dummy_label_with_same_size do |label|
43
+ @parent.tk_widget.update
44
+ label.tk_widget.winfo_width
45
+ end
46
+ end
47
+
48
+ def textwidth_and_maxwidth_in_pixel
49
+ create_dummy_frame_with_same_size_label do |frame, label|
50
+ { text_width: label.tk_widget.winfo_width,
51
+ max_width: frame.tk_widget.winfo_width }
52
+ end
53
+ end
54
+
55
+ def resize
56
+ max_width = cell_bbox[2]
57
+ text_width = text_width_in_pixel
58
+ new_width = [[@min_width, text_width + @add_width].max, max_width].min
59
+ tk_widget.width = 0
60
+ tk_widget.grid(ipadx: new_width / 2.0)
61
+ @parent.tk_widget.update
62
+ end
63
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TkWrapper::Widgets::Base end
4
+
5
+ require_relative 'widget'
6
+ require_relative 'manager'
7
+ require_relative 'configuration'
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "#{LIB_DIR}/util/hash_recursive.rb"
4
+
5
+ require_relative 'base'
6
+
7
+ class TkWrapper::Widgets::Base::Configuration
8
+ attr_accessor :config
9
+
10
+ GRID_SPECIAL_VALUES = {
11
+ onecell: {
12
+ weights: { rows: [1], cols: [1] }
13
+ },
14
+ fullcell: {
15
+ column: 0, row: 0, sticky: 'nsew'
16
+ },
17
+ default: {
18
+ weights: { rows: [1], cols: [1] },
19
+ column: 0, row: 0, sticky: 'nsew'
20
+ }
21
+ }.freeze
22
+
23
+ NON_TK_OPTIONS = %i[id tk_class tearoff weights menu].freeze
24
+
25
+ def initialize(config)
26
+ @config = parse!(config)
27
+ end
28
+
29
+ def merge!(*configurations)
30
+ configurations = configurations.map do |configuration|
31
+ next configuration.config if configuration.is_a?(self.class)
32
+
33
+ parse!(configuration)
34
+ end
35
+
36
+ Util.merge_recursive!(@config, *configurations)
37
+ end
38
+
39
+ def parse!(config)
40
+ Util.each_recursive(config) do |hash, key, value|
41
+ next if value.is_a?(Hash)
42
+
43
+ hash[key] = parse_value(key, value)
44
+ end
45
+
46
+ config
47
+ end
48
+
49
+ def [](key)
50
+ key = key.to_sym
51
+ return self.class.new(@config[key]) if @config[key].is_a?(Hash)
52
+
53
+ @config[key]
54
+ end
55
+
56
+ def []=(key, value)
57
+ key = key.to_sym
58
+ @config[key] = parse_value(key, value)
59
+ end
60
+
61
+ def parse_value(key, value)
62
+ case key
63
+ when :grid
64
+ parse_grid_value(value)
65
+ else
66
+ value
67
+ end
68
+ end
69
+
70
+ def parse_grid_value(value)
71
+ return GRID_SPECIAL_VALUES[value] if value.is_a?(Symbol)
72
+
73
+ value
74
+ end
75
+
76
+ def grid(only_tk_options: false)
77
+ return @config[:grid] unless only_tk_options
78
+
79
+ @config[:grid].reject { |option, _| NON_TK_OPTIONS.include?(option) }
80
+ end
81
+
82
+ def configure_tk_widget(tk_widget)
83
+ @config.each do |option, value|
84
+ next if NON_TK_OPTIONS.include?(option)
85
+
86
+ if option == :grid
87
+ grid = grid(only_tk_options: true)
88
+ next if grid.empty?
89
+ next tk_widget.grid(grid) if option == :grid
90
+ end
91
+
92
+ tk_widget[option] = value
93
+ end
94
+
95
+ configure_weights(tk_widget)
96
+ end
97
+
98
+ def configure_weights(tk_widget)
99
+ return unless (weights = @config.dig(:grid, :weights))
100
+
101
+ (weights[:rows] || []).each_with_index do |weight, index|
102
+ TkGrid.rowconfigure(tk_widget, index, weight: weight)
103
+ end
104
+
105
+ (weights[:cols] || []).each_with_index do |weight, index|
106
+ TkGrid.columnconfigure(tk_widget, index, weight: weight)
107
+ end
108
+ end
109
+
110
+ def configure_tearoff
111
+ return if (tearoff = @config[:tearoff]).nil?
112
+
113
+ TkOption.add '*tearOff', (tearoff ? 1 : 0)
114
+ end
115
+
116
+ def merge_global_configurations(manager, widget)
117
+ merge!(*manager.configurations(widget))
118
+ end
119
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ # manages widgets and their global configurations
4
+ class TkWrapper::Widgets::Base::Manager
5
+ def initialize
6
+ @configurations = []
7
+ @modifications = []
8
+ end
9
+
10
+ def add_configuration(matcher, configuration)
11
+ @configurations.push([matcher, configuration])
12
+ end
13
+
14
+ def add_modification(matcher, &callback)
15
+ @modifications.push([matcher, callback])
16
+ end
17
+
18
+ def configurations(widget)
19
+ @configurations.filter_map do |(matcher, config)|
20
+ config if widget.check_match(matcher)
21
+ end
22
+ end
23
+
24
+ def execute_modifications(widget)
25
+ @modifications.each do |(matcher, callback)|
26
+ next unless (match = widget.check_match(matcher))
27
+
28
+ arguments = match.is_a?(MatchData) ? [widget, match] : [widget]
29
+ callback.call(*arguments)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tk'
4
+
5
+ require "#{LIB_DIR}/tk_extensions"
6
+
7
+ require_relative 'base'
8
+
9
+ class TkWrapper::Widgets::Base::Widget
10
+ include TkExtensions
11
+
12
+ attr_accessor :config
13
+ attr_reader :parent, :childs
14
+
15
+ def tk_class() end
16
+
17
+ def self.manager
18
+ @manager ||= TkWrapper::Widgets::Base::Manager.new
19
+ end
20
+
21
+ def self.config(matcher, configuration)
22
+ manager.add_configuration(matcher, configuration)
23
+ end
24
+
25
+ def self.modify(matcher, &callback)
26
+ manager.add_modification(matcher, &callback)
27
+ end
28
+
29
+ def manager
30
+ TkWrapper::Widgets::Base::Widget.manager
31
+ end
32
+
33
+ def initialize(config: {}, childs: [])
34
+ @config = TkWrapper::Widgets::Base::Configuration.new(config)
35
+ @childs = childs.is_a?(Array) ? childs : [childs]
36
+ @id = config[:id]
37
+ end
38
+
39
+ def create_tk_widget(parent)
40
+ tk_class = @config[:tk_class] || self.tk_class
41
+
42
+ return unless tk_class
43
+
44
+ parent&.tk_widget ? tk_class.new(parent.tk_widget) : tk_class.new
45
+ end
46
+
47
+ # if parent is provided and self has no tk_class, the tk_widget of the
48
+ # parent is returned, if parent is not nil
49
+ def tk_widget(parent = @parent)
50
+ return @tk_widget if @tk_widget
51
+
52
+ (@tk_widget = create_tk_widget(parent)) || parent&.tk_widget
53
+ end
54
+
55
+ def build(parent, configure: true)
56
+ @parent = parent
57
+ tk_widget # creates the widget if possible and not yet created
58
+ self.configure if configure
59
+ manager.execute_modifications(self)
60
+ @childs.each { |child| child.build(self) }
61
+ end
62
+
63
+ def configure
64
+ @config.merge_global_configurations(manager, self)
65
+ @config.configure_tk_widget(tk_widget)
66
+ @config.configure_tearoff
67
+ end
68
+
69
+ def check_match(matcher)
70
+ case matcher
71
+ when Regexp
72
+ matcher.match(@id)
73
+ when String, Symbol
74
+ matcher == @id
75
+ when nil
76
+ true
77
+ else
78
+ is_a?(matcher)
79
+ end
80
+ end
81
+
82
+ def find(matcher)
83
+ nodes_to_scan = [self]
84
+ until nodes_to_scan.empty?
85
+ node = nodes_to_scan.pop
86
+ return node if node.check_match(matcher)
87
+
88
+ nodes_to_scan = node.childs + nodes_to_scan
89
+ end
90
+ end
91
+
92
+ def find_all(matcher)
93
+ found_nodes = []
94
+ nodes_to_scan = [self]
95
+
96
+ until nodes_to_scan.empty?
97
+ node = nodes_to_scan.pop
98
+ found_nodes.push(node) if node.check_match(matcher)
99
+
100
+ nodes_to_scan = node.childs + nodes_to_scan
101
+ end
102
+
103
+ found_nodes
104
+ end
105
+ end
106
+
107
+ # the first parent, which contains a tk_widget, which is really different
108
+ # from self.tk_widget
109
+ def get_container_parent
110
+ container = @parent
111
+ while container.tk_widget == tk_widget
112
+ return unless container.parent # not in a grid?
113
+
114
+ container = container.parent
115
+ end
116
+ container
117
+ end
118
+
119
+ # returns the bounding box of the tk_widget
120
+ def cell_bbox
121
+ return unless (container = get_container_parent)
122
+
123
+ grid_info = TkGrid.info(tk_widget)
124
+ start_col = grid_info['column']
125
+ end_col = start_col + grid_info['columnspan'] - 1
126
+ start_row = grid_info['row']
127
+ end_row = start_row + grid_info['rowspan'] - 1
128
+
129
+ TkGrid.bbox(container.tk_widget, start_col, start_row, end_col, end_row)
130
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TkWrapper::Widgets::Base::Widgets
4
+ def initialize
5
+ @configurations = []
6
+ @modifications = []
7
+ end
8
+
9
+ def add_configuration(matcher, configuration)
10
+ @configurations.push(matcher, configuration)
11
+ end
12
+
13
+ def add_modification(matcher, &callback)
14
+ @modifications.push(matcher, callback)
15
+ end
16
+
17
+ def configurations(widget)
18
+ @configurations.filter_map do |(matcher, config)|
19
+ config if widget.check_match(matcher)
20
+ end
21
+ end
22
+
23
+ def execute_modifications(widget)
24
+ @modifications.each do |(matcher, callback)|
25
+ next unless (match = widget.check_match(matcher))
26
+
27
+ arguments = match.is_a?(MatchData) ? [widget, match] : [widget]
28
+ callback.call(*arguments)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TkWrapper::Widgets::Entry < TkWrapper::Widgets::Base::Widget
4
+ def tk_class
5
+ TkWidgets::Entry
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TkWrapper::Widgets::Frame < TkWrapper::Widgets::Base::Widget
4
+ def tk_class
5
+ TkWidgets::Frame
6
+ end
7
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ # classification of TkGrid
4
+ class TkWrapper::Widgets::Grid < TkWrapper::Widgets::Base::Widget
5
+ attr_reader :parent, :matrix
6
+
7
+ def tk_class
8
+ TkWidgets::Frame
9
+ end
10
+
11
+ def initialize(config: {}, childs: [])
12
+ super(config: config, childs: childs)
13
+ @childs.map! { |row| row.is_a?(Array) ? row : [row] }
14
+ configure_cells_for_grid
15
+ @childs.flatten! && @childs.select! { |cell| cell.is_a?(Widget) }
16
+ end
17
+
18
+ def configure_cells_for_grid
19
+ @childs.each_with_index do |row, row_i|
20
+ row.each_with_index do |cell, col_i|
21
+ next unless cell.is_a?(Widget)
22
+
23
+ (cell.config[:grid] ||= {}).merge!({ row: row_i, column: col_i })
24
+
25
+ configure_colspan(cell, row_i, col_i)
26
+ configure_rowspan(cell, row_i, col_i)
27
+ end
28
+ end
29
+ end
30
+
31
+ def configure_colspan(cell, row_i, col_i)
32
+ cols_after_cell = @childs[row_i][(col_i + 1)..]
33
+ colspan = cols_after_cell.reduce(1) do |span, content|
34
+ content == :right ? span + 1 : (break span)
35
+ end
36
+ (cell.config[:grid] ||= {}).merge!({ columnspan: colspan })
37
+ end
38
+
39
+ def configure_rowspan(cell, row_i, col_i)
40
+ rows_after_cell = @childs[(row_i + 1)..]
41
+ rowspan = rows_after_cell.reduce(1) do |span, row|
42
+ row[col_i] == :bottom ? span + 1 : (break span)
43
+ end
44
+ (cell.config[:grid] ||= {}).merge!({ rowspan: rowspan })
45
+ end
46
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TkWrapper::Widgets::Label < TkWrapper::Widgets::Base::Widget
4
+ def tk_class
5
+ TkWidgets::Label
6
+ end
7
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TkWrapper::Widgets::Menu < TkWrapper::Widgets::Base::Widget
4
+ def tk_class
5
+ TkWidgets::TkMenu
6
+ end
7
+
8
+ def build(parent)
9
+ super(parent)
10
+ parent.tk_widget['menu'] = tk_widget
11
+ end
12
+
13
+ class Cascade < TkWrapper::Widgets::Base::Widget
14
+ def tk_class
15
+ TkWidgets::TkMenu
16
+ end
17
+
18
+ def build(parent)
19
+ super(parent, configure: false)
20
+ @config[:menu] = tk_widget
21
+ parent.tk_widget.add :cascade, **@config.config
22
+ end
23
+ end
24
+
25
+ class Command < TkWrapper::Widgets::Base::Widget
26
+ def build(parent)
27
+ parent.tk_widget.add :command, **@config.config
28
+ end
29
+ end
30
+
31
+ def self.create(structure: [], config: {})
32
+ Menu.new(
33
+ config: config,
34
+ childs: structure.map { |entry| create_subentry(entry) }
35
+ )
36
+ end
37
+
38
+ def self.create_subentries(structure)
39
+ return [] unless structure && !structure.nil?
40
+
41
+ structure = [structure] unless structure.is_a?(Array)
42
+ structure.map { |entry| create_subentry(**entry) }
43
+ end
44
+
45
+ def self.create_subentry(entry)
46
+ case entry
47
+ in { config: config, structure: structure }
48
+ return Cascade.new config: config, childs: create_subentries(structure)
49
+ else
50
+ return Command.new config: entry
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TkWrapper::Widgets::Root < TkWrapper::Widgets::Base::Widget
4
+ def initialize(**arguments)
5
+ super(**arguments)
6
+ build(nil)
7
+ end
8
+
9
+ def tk_class
10
+ TkWidgets::TkRoot
11
+ end
12
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TkWrapper::Widgets::Text < TkWrapper::Widgets::Base::Widget
4
+ def tk_class
5
+ TkWidgets::TkText
6
+ end
7
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TkWrapper::Widgets end
4
+
5
+ require_relative 'base/base'
6
+
7
+ require_relative 'root'
8
+ require_relative 'frame'
9
+ require_relative 'label'
10
+ require_relative 'menu'
11
+ require_relative 'text'
12
+ require_relative 'entry'
13
+ require_relative 'auto_resize_entry'
14
+ require_relative 'grid'
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tkwrapper
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Benjamin Schnitzler
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-12-12 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email: reception@e.mail.de
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/tk_extensions.rb
20
+ - lib/tkwrapper.rb
21
+ - lib/util/hash_recursive.rb
22
+ - lib/util/virtual_methods.rb
23
+ - lib/widgets/auto_resize_entry.rb
24
+ - lib/widgets/base/base.rb
25
+ - lib/widgets/base/configuration.rb
26
+ - lib/widgets/base/manager.rb
27
+ - lib/widgets/base/widget.rb
28
+ - lib/widgets/base/widgets.rb
29
+ - lib/widgets/entry.rb
30
+ - lib/widgets/frame.rb
31
+ - lib/widgets/grid.rb
32
+ - lib/widgets/label.rb
33
+ - lib/widgets/menu.rb
34
+ - lib/widgets/root.rb
35
+ - lib/widgets/text.rb
36
+ - lib/widgets/widgets.rb
37
+ homepage: https://github.com/bschnitz/tkwrapper
38
+ licenses:
39
+ - MIT
40
+ metadata: {}
41
+ post_install_message:
42
+ rdoc_options: []
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '3.0'
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ requirements: []
56
+ rubygems_version: 3.2.29
57
+ signing_key:
58
+ specification_version: 4
59
+ summary: Extensions/Wrapper ruby Tk
60
+ test_files: []