gui 0.0.1.pre1
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 +7 -0
- data/COPYING +202 -0
- data/README.md +10 -0
- data/lib/gui.rb +26 -0
- data/lib/gui/context.rb +130 -0
- data/lib/gui/geom.rb +228 -0
- data/lib/gui/selector.rb +66 -0
- data/lib/gui/selector/checks.rb +178 -0
- data/lib/gui/texture.rb +103 -0
- data/lib/gui/version.rb +36 -0
- data/lib/gui/view.rb +134 -0
- data/lib/gui/window.rb +149 -0
- metadata +157 -0
data/lib/gui/selector.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# Copyright 2014 Noel Cower
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
#
|
15
|
+
# -----------------------------------------------------------------------------
|
16
|
+
#
|
17
|
+
# selector.rb
|
18
|
+
# Selector chain class.
|
19
|
+
|
20
|
+
|
21
|
+
module GUI
|
22
|
+
|
23
|
+
class Selector
|
24
|
+
|
25
|
+
class << self
|
26
|
+
def view_matches_attr(view, name, value)
|
27
|
+
view.respond_to?(name) && view.__send__(name) == value
|
28
|
+
end
|
29
|
+
|
30
|
+
def build(selector_str)
|
31
|
+
|
32
|
+
end
|
33
|
+
end # singleton_class
|
34
|
+
|
35
|
+
# The next
|
36
|
+
attr_accessor :succ
|
37
|
+
# Array of proc/lambda objects that receive a view and return true if
|
38
|
+
# the view matches, otherwise nil/false
|
39
|
+
attr_accessor :attributes
|
40
|
+
attr_accessor :direct
|
41
|
+
|
42
|
+
def initialize
|
43
|
+
@succ = nil
|
44
|
+
@attributes = []
|
45
|
+
@direct = false
|
46
|
+
end
|
47
|
+
|
48
|
+
# Whether this selector matches a view.
|
49
|
+
def matches?(view)
|
50
|
+
attributes.empty? || attributes.all? { |sel_attr| sel_attr[view] }
|
51
|
+
end
|
52
|
+
|
53
|
+
def find_match(view)
|
54
|
+
# TODO: Grab leaves and test selectors in reverse order
|
55
|
+
further = direct ? nil : self
|
56
|
+
if matches?(view)
|
57
|
+
return view unless @succ
|
58
|
+
further = @succ
|
59
|
+
end
|
60
|
+
|
61
|
+
view.subviews.detect { |subview| further.find_match(subview) }
|
62
|
+
end
|
63
|
+
|
64
|
+
end # Selector
|
65
|
+
|
66
|
+
end # GUI
|
@@ -0,0 +1,178 @@
|
|
1
|
+
# Copyright 2014 Noel Cower
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
#
|
15
|
+
# -----------------------------------------------------------------------------
|
16
|
+
#
|
17
|
+
# checks.rb
|
18
|
+
# Selector checks (class/tag/attribute)
|
19
|
+
|
20
|
+
|
21
|
+
module GUI
|
22
|
+
|
23
|
+
#
|
24
|
+
# All selector checks (held by the attributes array of a selector) expect a
|
25
|
+
# call(view) method to be implemented. This returns either true or false values,
|
26
|
+
# though it's only necessary that they return things that evaluate to true or
|
27
|
+
# false. It is possible, therefore, to hand-write all your selectors rather than
|
28
|
+
# compiling them, which can be _much_ faster when performing lookups since
|
29
|
+
# attribute checks don't have to depend on reducing something by sending
|
30
|
+
# messages over and over.
|
31
|
+
#
|
32
|
+
# In practice, however, selectors are just slow in all my tests right now and I
|
33
|
+
# need to rewrite them to test starting with leaf views anyway (this should
|
34
|
+
# result in more specific matches and avoid recursive tests since it's then
|
35
|
+
# possible to iterate down through parents and cut off if a minimum depth isn't
|
36
|
+
# met [i.e., a selector with N views to match requires at least depth N, but
|
37
|
+
# can match views across depths greater than N if it has indirect matches]).
|
38
|
+
#
|
39
|
+
|
40
|
+
|
41
|
+
class ViewTagCheck
|
42
|
+
|
43
|
+
def initialize(tagname)
|
44
|
+
@tagname = tagname
|
45
|
+
end
|
46
|
+
|
47
|
+
def call(view)
|
48
|
+
view.tag == tagname
|
49
|
+
end
|
50
|
+
|
51
|
+
alias_method :[], :call
|
52
|
+
|
53
|
+
end # ViewTagCheck
|
54
|
+
|
55
|
+
|
56
|
+
class ViewClassCheck
|
57
|
+
|
58
|
+
def initialize(classname)
|
59
|
+
@classnames =
|
60
|
+
case classname
|
61
|
+
when Array then classname.dup
|
62
|
+
when Symbol then [classname]
|
63
|
+
when String then [classname.to_sym]
|
64
|
+
else raise ArgumentError, "Invalid class name type: #{classname.class}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def call(view)
|
69
|
+
klass = view.class
|
70
|
+
name = nil
|
71
|
+
while klass
|
72
|
+
name = ViewAttrCheck.extract_class_name(klass)
|
73
|
+
return true if @classnames.include?(name)
|
74
|
+
klass = klass.superclass
|
75
|
+
end
|
76
|
+
false
|
77
|
+
end
|
78
|
+
|
79
|
+
alias_method :[], :call
|
80
|
+
|
81
|
+
end # ViewClassCheck
|
82
|
+
|
83
|
+
|
84
|
+
class ViewAttrCheck
|
85
|
+
|
86
|
+
SCO_MARKER = '::'
|
87
|
+
KEYPATH_SEPARATOR = '.'
|
88
|
+
|
89
|
+
class << self
|
90
|
+
attr_accessor :__module_name_cache__
|
91
|
+
|
92
|
+
def extract_class_name(klass)
|
93
|
+
(__module_name_cache__ ||= {})[klass] ||= begin
|
94
|
+
# Cache classname symbols because string ops are slow
|
95
|
+
name = klass.name
|
96
|
+
sco_index = klass.rindex(SCO_MARKER)
|
97
|
+
if sco_index
|
98
|
+
klass.slice!(0 .. sco_index + 1)
|
99
|
+
end
|
100
|
+
name.to_sym
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end # singleton_class
|
104
|
+
|
105
|
+
def initialize(key, operator, operand)
|
106
|
+
@key = key.split(KEYPATH_SEPARATOR).map!(&:to_sym)
|
107
|
+
@operator = operator
|
108
|
+
@operand = operand
|
109
|
+
@is_string = @operand.kind_of?(String)
|
110
|
+
end
|
111
|
+
|
112
|
+
# NOTE: Deprecate and remove class checks for ViewAttrCheck? Might be a good
|
113
|
+
# idea, but it sort of remains since it's occasionally handy to do something
|
114
|
+
# like [content_view.class = Something]. Probably just going to remove this,
|
115
|
+
# though.
|
116
|
+
def class_check(klass)
|
117
|
+
# Cache Symbol for operand so I'm not converting it every time.
|
118
|
+
name = (@operand_sym ||= @operand.to_sym)
|
119
|
+
|
120
|
+
while klass
|
121
|
+
case @operator
|
122
|
+
when :equal
|
123
|
+
return true if self.class.extract_class_name(klass) == name
|
124
|
+
when :not_equal
|
125
|
+
return true unless self.class.extract_class_name(klass) == name
|
126
|
+
when :trueish # Necessarily true for classes.
|
127
|
+
true
|
128
|
+
else # Otherwise no test passes.
|
129
|
+
false
|
130
|
+
end
|
131
|
+
klass = klass.superclass
|
132
|
+
end
|
133
|
+
false
|
134
|
+
end
|
135
|
+
|
136
|
+
def call(view)
|
137
|
+
view_value = @key.reduce(view) { |value, msg| value.__send__(msg) }
|
138
|
+
|
139
|
+
if @is_string && !view_value.kind_of?(String)
|
140
|
+
view_value =
|
141
|
+
case view_value
|
142
|
+
when Class then return class_check(view_value) # return early
|
143
|
+
when Module then extract_class_name(view_value)
|
144
|
+
when Enumerable then
|
145
|
+
# There is a case here where doing something like
|
146
|
+
# `included_modules <- X` will fail because the values contained by
|
147
|
+
# the Enumerable are modules but the check won't know, so it'll just
|
148
|
+
# always fail. Thought about working around this by mapping modules
|
149
|
+
# to their extracted names, but decided I'll only do that if it turns
|
150
|
+
# out to be a problem.
|
151
|
+
return false unless @operator == :contains
|
152
|
+
view_value
|
153
|
+
else view_value.to_s
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
case operator
|
158
|
+
when :trueish then !!view_value
|
159
|
+
when :falseish then !view_value
|
160
|
+
when :equal then view_value == @operand
|
161
|
+
when :not_equal then view_value != @operand
|
162
|
+
when :greater then view_value > @operand
|
163
|
+
when :greater_equal then view_value >= @operand
|
164
|
+
when :lesser then view_value < @operand
|
165
|
+
when :lesser_equal then view_value <= @operand
|
166
|
+
when :contains
|
167
|
+
view_value.respond_to?(:include?) && view_value.include?(@operand)
|
168
|
+
else
|
169
|
+
raise SelectorError, "Invalid operator for ViewAttrCheck: #{operator}"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
alias_method :[], :call
|
174
|
+
|
175
|
+
end # ViewAttrCheck
|
176
|
+
|
177
|
+
end # GUI
|
178
|
+
|
data/lib/gui/texture.rb
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
# Copyright 2014 Noel Cower
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
#
|
15
|
+
# -----------------------------------------------------------------------------
|
16
|
+
#
|
17
|
+
# texture.rb
|
18
|
+
# Wrapper around GL texture names.
|
19
|
+
|
20
|
+
|
21
|
+
require 'stb-image'
|
22
|
+
require 'snow-data'
|
23
|
+
|
24
|
+
|
25
|
+
module GUI
|
26
|
+
|
27
|
+
class Texture
|
28
|
+
|
29
|
+
TextureName = Snow::CStruct.new { uint32_t :name }
|
30
|
+
|
31
|
+
LOAD_TEXTURE_BLOCK = -> (data, x, y, components) do
|
32
|
+
internalFormat =
|
33
|
+
case components
|
34
|
+
when STBI::COMPONENTS_GREY then Gl::GL_RED
|
35
|
+
when STBI::COMPONENTS_GREY_ALPHA then Gl::GL_RG
|
36
|
+
when STBI::COMPONENTS_RGB then Gl::GL_RGB
|
37
|
+
when STBI::COMPONENTS_RGB_ALPHA then Gl::GL_RGBA
|
38
|
+
else raise ArgumentError, "Invalid number of texture components"
|
39
|
+
end
|
40
|
+
|
41
|
+
name = TextureName.new
|
42
|
+
raise "Unable to allocate texture name" unless name
|
43
|
+
|
44
|
+
Gl::glGetIntegerv(Gl::GL_TEXTURE_BINDING_2D, name.address)
|
45
|
+
prev_name = name.name
|
46
|
+
|
47
|
+
Gl::glGenTextures(1, name.address)
|
48
|
+
Gl::glBindTexture(Gl::GL_TEXTURE_2D, name.name)
|
49
|
+
|
50
|
+
Gl::glTexParameteri(Gl::GL_TEXTURE_2D, Gl::GL_TEXTURE_WRAP_S, Gl::GL_CLAMP_TO_EDGE)
|
51
|
+
Gl::glTexParameteri(Gl::GL_TEXTURE_2D, Gl::GL_TEXTURE_WRAP_T, Gl::GL_CLAMP_TO_EDGE)
|
52
|
+
Gl::glTexParameteri(Gl::GL_TEXTURE_2D, Gl::GL_TEXTURE_MIN_FILTER, Gl::GL_LINEAR)
|
53
|
+
Gl::glTexParameteri(Gl::GL_TEXTURE_2D, Gl::GL_TEXTURE_MAG_FILTER, Gl::GL_LINEAR)
|
54
|
+
|
55
|
+
Gl::glTexImage2D(
|
56
|
+
Gl::GL_TEXTURE_2D, # target
|
57
|
+
0, # level
|
58
|
+
format, # internal format
|
59
|
+
x, y, # width, height
|
60
|
+
0, # border
|
61
|
+
format, # format
|
62
|
+
Gl::GL_UNSIGNED_BYTE, # typep
|
63
|
+
data
|
64
|
+
)
|
65
|
+
|
66
|
+
glBindTexture(Gl::GL_TEXTURE_2D, prev_name)
|
67
|
+
|
68
|
+
name
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
# Allocates a new texture using the given IO object.
|
73
|
+
# If a block is given, the texture is only valid in the scope of the block
|
74
|
+
# unless retained elsewhere.
|
75
|
+
def initialize(io, &block)
|
76
|
+
@name = STBI.load_image(io, STBI::COMPONENTS_DEFAULT, &LOAD_TEXTURE_BLOCK)
|
77
|
+
@refs = 0
|
78
|
+
retain(&block)
|
79
|
+
end
|
80
|
+
|
81
|
+
def retain
|
82
|
+
@refs += 1
|
83
|
+
|
84
|
+
if block_given?
|
85
|
+
yield self
|
86
|
+
release
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def release
|
91
|
+
@refs -= 1
|
92
|
+
if @refs == 0
|
93
|
+
# If it's necessary to do anything else to release the object, pass it
|
94
|
+
# to a block first.
|
95
|
+
yield self if block_given?
|
96
|
+
|
97
|
+
glDeleteTextures(1, @name.address)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end # Texture
|
102
|
+
|
103
|
+
end # GUI
|
data/lib/gui/version.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# Copyright 2014 Noel Cower
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
#
|
15
|
+
# -----------------------------------------------------------------------------
|
16
|
+
#
|
17
|
+
# version.rb
|
18
|
+
# Version information for the gui gem.
|
19
|
+
|
20
|
+
|
21
|
+
module GUI
|
22
|
+
|
23
|
+
GUI_VERSION = '0.0.1.pre1'.freeze
|
24
|
+
GUI_LICENSE_BRIEF = 'Apache 2.0 License'.freeze
|
25
|
+
GUI_GEM_ROOT = File.expand_path('../../../', __FILE__).freeze
|
26
|
+
|
27
|
+
# Don't load the license unless it's needed.
|
28
|
+
define_singleton_method(:GUI_LICENSE_FULL, &-> do
|
29
|
+
File.open("#{GUI_GEM_ROOT}/COPYING") do |io|
|
30
|
+
io.read
|
31
|
+
end.freeze.tap do |license_txt|
|
32
|
+
::GUI::set_const(:GUI_LICENSE_FULL, license_txt)
|
33
|
+
end
|
34
|
+
end)
|
35
|
+
|
36
|
+
end # GUI
|
data/lib/gui/view.rb
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
# Copyright 2014 Noel Cower
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
#
|
15
|
+
# -----------------------------------------------------------------------------
|
16
|
+
#
|
17
|
+
# widget.rb
|
18
|
+
# Base class for view types.
|
19
|
+
|
20
|
+
|
21
|
+
require 'gui/geom'
|
22
|
+
|
23
|
+
|
24
|
+
module GUI
|
25
|
+
|
26
|
+
class View
|
27
|
+
|
28
|
+
# View tag (default: nil)
|
29
|
+
attr_accessor :tag
|
30
|
+
|
31
|
+
# Subviews held by the view. Should not be modified directly. Instead, to
|
32
|
+
# add a subview, use add_view.
|
33
|
+
attr_reader :subviews
|
34
|
+
|
35
|
+
# Rectangular portion
|
36
|
+
attr_accessor :frame # Rect
|
37
|
+
|
38
|
+
def initialize(frame = nil)
|
39
|
+
@needs_layout = false
|
40
|
+
@invalidated = nil
|
41
|
+
@subviews = []
|
42
|
+
@attributes = []
|
43
|
+
@superview = nil
|
44
|
+
@tag = nil
|
45
|
+
@frame = frame || Rect.new
|
46
|
+
|
47
|
+
invalidate
|
48
|
+
request_layout
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns the containing superview of the view.
|
52
|
+
def superview
|
53
|
+
@superview
|
54
|
+
end
|
55
|
+
|
56
|
+
# Sets the containing superview of the view. This invalidates and requests
|
57
|
+
# layout on the previous superview, if any.
|
58
|
+
def superview=(new_superview)
|
59
|
+
old_superview = @superview
|
60
|
+
if !old_superview.nil?
|
61
|
+
old_superview.delete(self)
|
62
|
+
old_superview.invalidate(@frame.dup)
|
63
|
+
old_superview.request_layout
|
64
|
+
end
|
65
|
+
|
66
|
+
@superview = new_superview
|
67
|
+
if !new_superview.nil?
|
68
|
+
new_superview.children << self
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def add_view(view)
|
73
|
+
raise ArgumentError, "View already has a superview" if view.superview
|
74
|
+
view.superview = self
|
75
|
+
end
|
76
|
+
|
77
|
+
def bounds
|
78
|
+
@frame.with_origin(0, 0)
|
79
|
+
end
|
80
|
+
|
81
|
+
def remove_from_superview
|
82
|
+
self.superview = nil
|
83
|
+
end
|
84
|
+
|
85
|
+
def invalidated_region
|
86
|
+
@invalidated
|
87
|
+
end
|
88
|
+
|
89
|
+
def invalidate(region = nil)
|
90
|
+
if @invalidated
|
91
|
+
@invalidated.contains_both!(region || @frame)
|
92
|
+
else
|
93
|
+
@invalidated = (region || @frame.with_origin(0, 0)).dup
|
94
|
+
end.intersection!(bounds)
|
95
|
+
|
96
|
+
self
|
97
|
+
end
|
98
|
+
|
99
|
+
def request_layout
|
100
|
+
@needs_layout = true
|
101
|
+
end
|
102
|
+
|
103
|
+
def needs_layout?
|
104
|
+
@needs_layout
|
105
|
+
end
|
106
|
+
|
107
|
+
def perform_layout
|
108
|
+
end
|
109
|
+
|
110
|
+
def view_with_tag(tag)
|
111
|
+
if @tag == tag
|
112
|
+
self
|
113
|
+
else
|
114
|
+
@subviews.detect { |subview| subview.view_with_tag(tag) }
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def view_with_selector(selector)
|
119
|
+
nil
|
120
|
+
end
|
121
|
+
|
122
|
+
def [](selector)
|
123
|
+
case selector
|
124
|
+
when Symbol then view_with_tag(selector)
|
125
|
+
when Selector then view_with_selector(selector)
|
126
|
+
when String then view_with_selector(Selector.build(selector))
|
127
|
+
when Numeric then @subviews[selector]
|
128
|
+
else raise ArgumentError, "Invalid selector for View#[]"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
end # View
|
133
|
+
|
134
|
+
end # GUI
|