# Copyright (c) 2021-2022 Andy Maleh # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. require 'glimmer/fiddle_consumer' module Glimmer module LibUI ICON = File.expand_path('../../icons/blank.png', __dir__) class << self include Glimmer::FiddleConsumer def integer_to_boolean(int, allow_nil: true, allow_boolean: true) int.nil? ? (allow_nil ? nil : false) : (allow_boolean && (int.is_a?(TrueClass) || int.is_a?(FalseClass)) ? int : (int.is_a?(Integer) ? int == 1 : (allow_nil ? nil : false))) end def boolean_to_integer(bool, allow_nil: true, allow_integer: true) bool.nil? ? (allow_nil ? nil : 0) : (allow_integer && bool.is_a?(Integer) ? bool : (bool.is_a?(TrueClass) || bool.is_a?(FalseClass) ? (bool == true ? 1 : 0) : (allow_nil ? nil : 0))) end def degrees_to_radians(degrees) ((Math::PI * 2.0) / 360.00) * degrees.to_f end def interpret_color(value) if value.is_a?(Array) && value.last.is_a?(Hash) options = value.last value = value[0...-1] end value = value.first if value.is_a?(Array) && value.size == 1 value = value[:color] if value.is_a?(Hash) && value[:color] value = value.to_s if value.is_a?(Symbol) result = if value.is_a?(Array) old_value = value value = { r: value[0], g: value[1], b: value[2], } value[:a] = value[3] unless value[3].nil? value elsif value.is_a?(Hash) old_value = value value = old_value.dup value[:r] = value.delete(:red) if value[:red] value[:g] = value.delete(:green) if value[:green] value[:b] = value.delete(:blue) if value[:blue] value[:a] = value.delete(:alpha) if value[:alpha] value elsif value.is_a?(String) && !value.start_with?('0x') && !value.start_with?('#') && !value.downcase.match(/^((([1-9a-f]){6})|(([1-9a-f]){3}))$/) color = Color::RGB.extract_colors(value).first color.nil? ? {} : { r: color.red, g: color.green, b: color.blue, } else hex_to_rgb(value) end result.merge!(options) if options result end def hex_to_rgb(value) if value.is_a?(String) value = value[2..-1] if value.start_with?('0x') value = value[1..-1] if value.start_with?('#') value = value.chars.map {|char| [char, char]}.flatten.join if value.length == 3 value = value.to_i(16) end if value.is_a?(Integer) hex_value = value value = { r: ((hex_value >> 16) & 0xFF), g: ((hex_value >> 8) & 0xFF), b: (hex_value & 0xFF), } end value end def enum_symbols(enum_name) enum_symbol_values(enum_name).keys end def enum_names [ :align, :at, :attribute_type, :draw_brush_type, :draw_fill_mode, :draw_line_cap, :draw_line_join, :draw_text_align, :ext_key, :modifier, :table_model_column, :table_value_type, :text_italic, :text_stretch, :text_weight, :underline, :underline_color ] end # Returns ruby underscored symbols for enum values starting with enum name (camelcase, e.g. 'ext_key') def enum_symbol_values(enum_name) enum_name = enum_name.to_s.underscore.to_sym @enum_symbols ||= {} @enum_symbols[enum_name] ||= ::LibUI.constants.select do |c| c.to_s.match(/#{enum_name.to_s.camelcase(:upper)}[A-Z]/) end.map do |c| [c.to_s.underscore.sub("#{enum_name}_", '').to_sym, ::LibUI.const_get(c)] end.reject do |key, value| enum_name == :underline && key.to_s.start_with?('color') end.to_h end def enum_value_to_symbol(enum_name, enum_value) enum_symbol_values(enum_name).invert[enum_value] end def enum_symbol_to_value(enum_name, enum_symbol, default_symbol: nil, default_index: 0) if enum_symbol.is_a?(Integer) enum_symbol elsif enum_symbols(enum_name).include?(enum_symbol.to_s.to_sym) enum_symbol_values(enum_name)[enum_symbol.to_s.to_sym] elsif default_symbol enum_symbol_to_value(enum_name, default_symbol) else enum_symbol_to_value(enum_name, enum_symbols(enum_name)[default_index]) end end def x11_colors Color::RGB.constants.reject {|c| c.to_s.upcase == c.to_s}.map(&:to_s).map(&:underscore).map(&:to_sym) end # Queues block to execute at the nearest opportunity possible on the main GUI event loop def queue_main(&block) closure = fiddle_closure_block_caller(4, [0]) do result = boolean_to_integer(block.call) result = 1 if result.nil? result end ::LibUI.queue_main(closure) closure end # Calls block on the main GUI event loop after time_in_seconds delay, repeating indefinitely by default # If `repeat:` keyword arg is passed with an Integer value, it repeats for that number of times # If `repeat:` keyword arg is passed with false or 0, then the block is only called once # If block returns false at any point, the timer is stopped from further repetitions regardless of `repeat:` keyword arg value # If block returns true at any point, the timer continues for another repetition regardless of `repeat:` keyword arg value def timer(time_in_seconds = 0.1, repeat: true, &block) closure = fiddle_closure_block_caller(4, [0]) do result = boolean_to_integer(block.call, allow_integer: false) repeat -= 1 if repeat.is_a?(Integer) if result.nil? if (repeat == true || (repeat.is_a?(Integer) && repeat > 0)) result = 1 else result = 0 end end result end ::LibUI.timer(time_in_seconds * 1000.0, closure) closure end def respond_to?(method_name, *args) super || ::LibUI.respond_to?(method_name, *args) end def method_missing(method_name, *args, &block) if ::LibUI.respond_to?(method_name, true) ::LibUI.send(method_name, *args, &block) else super end end end end end