-- This is generated code. See https://reapertoolkit.dev/ for more info. -- version: 1.4.0 -- build: Mon Oct 9 17:47:09 UTC 2023 __RTK_VERSION='1.4.0' rtk=(function() __mod_rtk_core=(function() __mod_rtk_log=(function() local log={levels={[50]='CRITICAL',[40]='ERROR',[30]='WARNING',[20]='INFO',[10]='DEBUG',[9]='DEBUG2',},level=40,timer_threshold=20,named_timers=nil,timers={},queue={},wall_time_start=os.time(),reaper_time_start=reaper.time_precise(),}log.wall_time_start=log.wall_time_start+(log.reaper_time_start-math.floor(log.reaper_time_start))log.CRITICAL=50 log.ERROR=40 log.WARNING=30 log.INFO=20 log.DEBUG=10 log.DEBUG2=9 function log.critical(fmt,...)log._log(log.CRITICAL,nil,fmt,...)end function log.error(fmt,...)log._log(log.ERROR,nil,fmt,...)end function log.warning(fmt,...)log._log(log.WARNING,nil,fmt,...)end function log.info(fmt,...)log._log(log.INFO,nil,fmt,...)end function log.debug(fmt,...)log._log(log.DEBUG,nil,fmt,...)end function log.debug2(fmt,...)log._log(log.DEBUG2,nil,fmt,...)end local function enqueue(msg)local qlen=#log.queue if qlen==0 then reaper.defer(log.flush)end log.queue[qlen+1]=msg end local function _get_precise_duration_string(t)if t<0.1 then return string.format('%.03f', t)elseif t<1 then return string.format('%.02f', t)elseif t<10 then return string.format('%.01f', t)else return string.format('%.0f', t)end end function log.exception(fmt,...)log._log(log.ERROR,debug.traceback(),fmt,...)log.flush()end function log.trace(level)if log.level<=(level or log.DEBUG)then enqueue(debug.traceback() .. '\n')end end function log._log(level,tail,fmt,...)if level0 then local timer=log.timers[#log.timers] local total=_get_precise_duration_string((now-timer[1])*1000)local last=_get_precise_duration_string((now-timer[2])*1000)local name=timer[3] and string.format(' [%s]', timer[3]) or ''prefix=prefix .. string.format('(%s / %s ms%s) ', last, total, name)timer[2]=now end local msg=prefix .. err .. '\n'if tail then msg=msg .. tail .. '\n'end enqueue(msg)end function log.log(level,fmt,...)return log._log(level,nil,fmt,...)end function log.logf(level,fmt,func)if level>=log.level then return log._log(level,nil,fmt,func())end end function log.flush()local str=table.concat(log.queue)if #str>0 then reaper.ShowConsoleMsg(str)end log.queue={}end function log.level_name(level)return log.levels[level or log.level] or 'UNKNOWN'end function log.clear(level)if not level or log.level<=level then reaper.ShowConsoleMsg("")log.queue={}end end function log.time_start(name)if log.level>log.timer_threshold then return end local now=reaper.time_precise()table.insert(log.timers,{now,now,name})if name then if not log.named_timers then log.named_timers={}log.named_timers_order={}end if not log.named_timers[name] then log.named_timers[name]={0,0}log.named_timers_order[#log.named_timers_order+1]=name end end end function log.time_end(fmt,...)if fmt then log._log(log.DEBUG,nil,fmt,...)end log.time_end_report_if(false)end function log.time_end_report(fmt,...)if fmt then log._log(log.DEBUG,nil,fmt,...)end log.time_end_report_if(true)end function log.time_end_report_if(show,fmt,...)if log.level>log.timer_threshold then return end if fmt and show then log._log(log.DEBUG,nil,fmt,...)end assert(#log.timers > 0, "time_end() with no previous time_start()")local t0,_,name=table.unpack(table.remove(log.timers))if log.named_timers then if name then local delta=reaper.time_precise()-t0 local current=log.named_timers[name] if not current then log.named_timers[name]={current+delta,1}else log.named_timers[name]={current[1]+delta,current[2]+1}end end if show and log.level<=log.INFO then local output=''local maxname=0 local maxtime=0 local times={}for i,name in ipairs(log.named_timers_order)do local duration,_=table.unpack(log.named_timers[name])times[#times+1]=string.format('%.4f ms', duration * 1000)maxtime=math.max(maxtime,#times[#times])maxname=math.max(maxname,#name)end local fmt=string.format(' %%2d. %%%ds: %%%ds (%%d)\n', maxname, maxtime)for i,name in ipairs(log.named_timers_order)do local _,count=table.unpack(log.named_timers[name])output=output..string.format(fmt,i,name,times[i],count)end enqueue(output)end end if #log.timers==0 then log.named_timers=nil end end return log end)() local log=__mod_rtk_log local rtk={touchscroll=false,smoothscroll=true,touch_activate_delay=0.1,long_press_delay=0.5,double_click_delay=0.5,tooltip_delay=0.5,light_luma_threshold=0.6,debug=false,window=nil,has_js_reascript_api=(reaper.JS_Window_GetFocus~=nil),has_sws_extension=(reaper.BR_Win32_GetMonitorRectFromRect~=nil),script_path=nil,reaper_hwnd=nil,tick=0,fps=30,focused_hwnd=nil,focused=nil,theme=nil,_dest_stack={},_image_paths={},_animations={},_animations_len=0,_easing_functions={},_frame_count=0,_frame_time=nil,_modal=nil,_touch_activate_event=nil,_last_traceback=nil,_last_error=nil,_quit=false,_refs=setmetatable({}, {__mode='v'}),_run_soon=nil,_reactive_attr={},}rtk.scale=setmetatable({user=nil,_user=1.0,system=nil,reaper=1.0,framebuffer=nil,value=1.0,_discover=function()local inifile=reaper.get_ini_file()local ini,err=rtk.file.read(inifile)if not err then rtk.scale.reaper = ini:match('uiscale=([^\n]*)') or 1.0 end local ok, dpi=reaper.ThemeLayout_GetLayout("mcp", -3)if not ok then return end dpi=math.ceil(tonumber(dpi)/rtk.scale.reaper)rtk.scale.system=dpi/256.0 if not rtk.scale.framebuffer then if rtk.os.mac and dpi==512 then rtk.scale.framebuffer=2 else rtk.scale.framebuffer=1 end end rtk.scale._calc()end,_calc=function()local value=rtk.scale.user*rtk.scale.system*rtk.scale.reaper rtk.scale.value=math.ceil(value*100)/100.0 end,},{__index=function(t,key)return key=='user' and t._user or nil end,__newindex=function(t,key,value)if key=='user' then if value~=t._user then t._user=value rtk.scale._calc()if rtk.window then rtk.window:queue_reflow()end end else rawset(t,key,value)end end })rtk.dnd={dragging=nil,droppable=nil,dropping=nil,arg=nil,buttons=nil,}local _os=reaper.GetOS():lower():sub(1,3)rtk.os={mac = (_os == 'osx' or _os == 'mac'),windows=(_os=='win'),linux = (_os == 'lin' or _os == 'oth'),bits=32,}rtk.mouse={BUTTON_LEFT=1,BUTTON_MIDDLE=64,BUTTON_RIGHT=2,BUTTON_MASK=(1|2|64),x=0,y=0,down=0,state={order={},latest=nil},last={},}local _load_cursor if rtk.has_js_reascript_api then function _load_cursor(cursor)return reaper.JS_Mouse_LoadCursor(cursor)end else function _load_cursor(cursor)return cursor end end rtk.mouse.cursors={UNDEFINED=0,POINTER=_load_cursor(32512),BEAM=_load_cursor(32513),LOADING=_load_cursor(32514),CROSSHAIR=_load_cursor(32515),UP_ARROW=_load_cursor(32516),SIZE_NW_SE=_load_cursor(rtk.os.linux and 32643 or 32642),SIZE_SW_NE=_load_cursor(rtk.os.linux and 32642 or 32643),SIZE_EW=_load_cursor(32644),SIZE_NS=_load_cursor(32645),MOVE=_load_cursor(32646),INVALID=_load_cursor(32648),HAND=_load_cursor(32649),POINTER_LOADING=_load_cursor(32650),POINTER_HELP=_load_cursor(32651),REAPER_FADEIN_CURVE=_load_cursor(105),REAPER_FADEOUT_CURVE=_load_cursor(184),REAPER_CROSSFADE=_load_cursor(463),REAPER_DRAGDROP_COPY=_load_cursor(182),REAPER_DRAGDROP_RIGHT=_load_cursor(1011),REAPER_POINTER_ROUTING=_load_cursor(186),REAPER_POINTER_MOVE=_load_cursor(187),REAPER_POINTER_MARQUEE_SELECT=_load_cursor(488),REAPER_POINTER_DELETE=_load_cursor(464),REAPER_POINTER_LEFTRIGHT=_load_cursor(465),REAPER_POINTER_ARMED_ACTION=_load_cursor(434),REAPER_MARKER_HORIZ=_load_cursor(188),REAPER_MARKER_VERT=_load_cursor(189),REAPER_ADD_TAKE_MARKER=_load_cursor(190),REAPER_TREBLE_CLEF=_load_cursor(191),REAPER_BORDER_LEFT=_load_cursor(417),REAPER_BORDER_RIGHT=_load_cursor(418),REAPER_BORDER_TOP=_load_cursor(419),REAPER_BORDER_BOTTOM=_load_cursor(421),REAPER_BORDER_LEFTRIGHT=_load_cursor(450),REAPER_VERTICAL_LEFTRIGHT=_load_cursor(462),REAPER_GRID_RIGHT=_load_cursor(460),REAPER_GRID_LEFT=_load_cursor(461),REAPER_HAND_SCROLL=_load_cursor(429),REAPER_FIST_LEFT=_load_cursor(430),REAPER_FIST_RIGHT=_load_cursor(431),REAPER_FIST_BOTH=_load_cursor(453),REAPER_PENCIL=_load_cursor(185),REAPER_PENCIL_DRAW=_load_cursor(433),REAPER_ERASER=_load_cursor(472),REAPER_BRUSH=_load_cursor(473),REAPER_ARP=_load_cursor(502),REAPER_CHORD=_load_cursor(503),REAPER_TOUCHSEL=_load_cursor(515),REAPER_SWEEP=_load_cursor(517),REAPER_FADEIN_CURVE_ALT=_load_cursor(525),REAPER_FADEOUT_CURVE_ALT=_load_cursor(526),REAPER_XFADE_WIDTH=_load_cursor(528),REAPER_XFADE_CURVE=_load_cursor(529),REAPER_EXTMIX_SECTION_RESIZE=_load_cursor(530),REAPER_EXTMIX_MULTI_RESIZE=_load_cursor(531),REAPER_EXTMIX_MULTISECTION_RESIZE=_load_cursor(532),REAPER_EXTMIX_RESIZE=_load_cursor(533),REAPER_EXTMIX_ALLSECTION_RESIZE=_load_cursor(534),REAPER_EXTMIX_ALL_RESIZE=_load_cursor(535),REAPER_ZOOM=_load_cursor(1009),REAPER_INSERT_ROW=_load_cursor(1010),REAPER_RAZOR=_load_cursor(599),REAPER_RAZOR_MOVE=_load_cursor(600),REAPER_RAZOR_ADD=_load_cursor(601),REAPER_RAZOR_ENVELOPE_VERTICAL=_load_cursor(202),REAPER_RAZOR_ENVELOPE_RIGHT_TILT=_load_cursor(203),REAPER_RAZOR_ENVELOPE_LEFT_TILT=_load_cursor(204),}local FONT_FLAG_BOLD=string.byte('b')local FONT_FLAG_ITALICS=string.byte('i') << 8 local FONT_FLAG_UNDERLINE=string.byte('u') << 16 rtk.font={BOLD=FONT_FLAG_BOLD,ITALICS=FONT_FLAG_ITALICS,UNDERLINE=FONT_FLAG_UNDERLINE,multiplier=1.0 }rtk.keycodes={UP=30064,DOWN=1685026670,LEFT=1818584692,RIGHT=1919379572,RETURN=13,ENTER=13,SPACE=32,BACKSPACE=8,ESCAPE=27,TAB=9,HOME=1752132965,END=6647396,INSERT=6909555,DELETE=6579564,F1=26161,F2=26162,F3=26163,F4=26164,F5=26165,F6=26166,F7=26167,F8=26168,F9=26169,F10=6697264,F11=6697265,F12=6697266,}rtk.themes={dark={name='dark',dark=true,light=false,bg='#252525',default_font={'Calibri', 18},accent='#47abff',accent_subtle='#306088',tooltip_bg='#ffffff',tooltip_text='#000000',tooltip_font={'Segoe UI (TrueType)', 16},text='#ffffff',text_faded='#bbbbbb',text_font=nil,button='#555555',heading=nil,heading_font={'Calibri', 26},button_label='#ffffff',button_font=nil,button_gradient_mul=1,button_tag_alpha=0.32,button_normal_gradient=-0.37,button_normal_border_mul=0.7,button_hover_gradient=0.17,button_hover_brightness=0.9,button_hover_mul=1,button_hover_border_mul=1.1,button_clicked_gradient=0.47,button_clicked_brightness=0.9,button_clicked_mul=0.85,button_clicked_border_mul=1,entry_font=nil,entry_bg='#5f5f5f7f',entry_placeholder='#ffffff7f',entry_border_hover='#3a508e',entry_border_focused='#4960b8',entry_selection_bg='#0066bb',popup_bg=nil,popup_overlay='#00000040',popup_bg_brightness=1.3,popup_shadow='#11111166',popup_border='#385074',slider='#2196f3',slider_track='#5a5a5a',slider_font=nil,slider_tick_label=nil,},light={name='light',light=true,dark=false,accent='#47abff',accent_subtle='#a1d3fc',bg='#dddddd',default_font={'Calibri', 18},tooltip_font={'Segoe UI (TrueType)', 16},tooltip_bg='#ffffff',tooltip_text='#000000',button='#dedede',button_label='#000000',button_gradient_mul=1,button_tag_alpha=0.15,button_normal_gradient=-0.28,button_normal_border_mul=0.85,button_hover_gradient=0.12,button_hover_brightness=1,button_hover_mul=1,button_hover_border_mul=0.9,button_clicked_gradient=0.3,button_clicked_brightness=1.0,button_clicked_mul=0.9,button_clicked_border_mul=0.7,text='#000000',text_faded='#555555',heading_font={'Calibri', 26},entry_border_hover='#3a508e',entry_border_focused='#4960b8',entry_bg='#00000020',entry_placeholder='#0000007f',entry_selection_bg='#9fcef4',popup_bg=nil,popup_bg_brightness=1.5,popup_shadow='#11111122',popup_border='#385074',slider='#2196f3',slider_track='#5a5a5a',}}local function _postprocess_theme()local iconstyle=rtk.color.get_icon_style(rtk.theme.bg)rtk.theme.iconstyle=iconstyle for k,v in pairs(rtk.theme)do if type(v) == 'string' and v:byte(1) == 35 then rtk.theme[k]={rtk.color.rgba(v)}end end end function rtk.add_image_search_path(path,iconstyle)path=path:gsub('[/\\]$', '') .. '/'if not path:match('^%a:') and not path:match('^[\\/]') then path=rtk.script_path..path end if iconstyle then assert(iconstyle == 'dark' or iconstyle == 'light', 'iconstyle must be either light or dark')else iconstyle='nostyle'end local paths=rtk._image_paths[iconstyle] if not paths then paths={}rtk._image_paths[iconstyle]=paths end paths[#paths+1]=path end function rtk.set_theme(name,overrides)name=name or rtk.theme.name assert(rtk.themes[name], 'rtk: theme "' .. name .. '" does not exist in rtk.themes')rtk.theme={}table.merge(rtk.theme,rtk.themes[name])if overrides then table.merge(rtk.theme,overrides)end _postprocess_theme()end function rtk.set_theme_by_bgcolor(color,overrides)local name=rtk.color.luma(color) > rtk.light_luma_threshold and 'light' or 'dark'overrides=overrides or {}overrides.bg=color rtk.set_theme(name,overrides)end function rtk.set_theme_overrides(overrides)for _, name in ipairs({'dark', 'light'}) do if overrides[name] then rtk.themes[name]=table.merge(rtk.themes[name],overrides[name])if rtk.theme[name] then rtk.theme=table.merge(rtk.theme,overrides[name])end overrides[name]=nil end end rtk.themes.dark=table.merge(rtk.themes.dark,overrides)rtk.themes.light=table.merge(rtk.themes.light,overrides)rtk.theme=table.merge(rtk.theme,overrides)_postprocess_theme()end function rtk.new_theme(name,base,overrides)assert(not base or rtk.themes[base], string.format('base theme %s not found', base))assert(not rtk.themes[name], string.format('theme %s already exists', name))local theme=base and table.shallow_copy(rtk.themes[base])or {}rtk.themes[name]=table.merge(theme,overrides or {})end function rtk.add_modal(...)if rtk._modal==nil then rtk._modal={}end local state=rtk.mouse.state[rtk.mouse.state.latest] if state then state.modaltick=rtk.tick end local widgets={...}for _,widget in ipairs(widgets)do rtk._modal[widget.id]={widget,rtk.tick}end end function rtk.is_modal(widget)if widget==nil then return rtk._modal~=nil elseif rtk._modal then local w=widget while w do if rtk._modal[w.id]~=nil then return true end w=w.parent end end return false end function rtk.reset_modal()rtk._modal=nil end function rtk.pushdest(dest)rtk._dest_stack[#rtk._dest_stack+1]=gfx.dest gfx.dest=dest end function rtk.popdest()gfx.dest=table.remove(rtk._dest_stack,#rtk._dest_stack)end local function _handle_error(err)rtk._last_error=err rtk._last_traceback=debug.traceback()end function rtk.onerror(err,traceback)log.error("fatal: %s\n%s", err, traceback)log.flush()error(err)end function rtk.call(func,...)if rtk._quit then return end local ok,result=xpcall(func,_handle_error,...)if not ok then rtk.onerror(rtk._last_error,rtk._last_traceback)return end return result end function rtk.defer(func,...)if rtk._quit then return end local args=table.pack(...)reaper.defer(function()rtk.call(func,table.unpack(args,1,args.n))end)end function rtk.callsoon(func,...)if not rtk.window or not rtk.window.running then return rtk.defer(func,...)end local funcs=rtk._soon_funcs if not funcs then funcs={}rtk._soon_funcs=funcs end funcs[#funcs+1]={func,table.pack(...)}end function rtk._run_soon()local funcs=rtk._soon_funcs rtk._soon_funcs=nil for i=1,#funcs do local func,args=table.unpack(funcs[i])func(table.unpack(args,1,args.n))end end function rtk.callafter(duration,func,...)local args=table.pack(...)local start=reaper.time_precise()local function sched()if reaper.time_precise()-start>=duration then rtk.call(func,table.unpack(args,1,args.n))elseif not rtk._quit then reaper.defer(sched)end end sched()end function rtk.quit()if rtk.window and rtk.window.running then rtk.window:close()end rtk._quit=true end rtk.version={_DEFAULT_API=1,string=nil,api=nil,major=nil,minor=nil,patch=nil,}function rtk.version.parse()local ver=__RTK_VERSION or string.format('%s.99.99', rtk.version._DEFAULT_API)local parts=ver:split('.')rtk.version.major=tonumber(parts[1])rtk.version.minor=tonumber(parts[2])rtk.version.patch=tonumber(parts[3])rtk.version.api=rtk.version.major rtk.version.string=ver end function rtk.version.check(major,minor,patch)local v=rtk.version return v.major>major or (v.major==major and(not minor or v.minor>minor))or (v.major==major and v.minor==minor and(not patch or v.patch>=patch))end return rtk end)() local rtk=__mod_rtk_core __mod_rtk_type=(function() local rtk=__mod_rtk_core __mod_rtk_middleclass=(function() local middleclass={_VERSION='middleclass v4.1.1',}local function _createIndexWrapper(aClass,f)if f==nil then return aClass.__instanceDict else return function(self,name)local value=aClass.__instanceDict[name] if value~=nil then return value elseif type(f)=="function" then return(f(self,name))else return f[name] end end end end local function _propagateInstanceMethod(aClass,name,f)f=name=="__index" and _createIndexWrapper(aClass, f) or f aClass.__instanceDict[name]=f for subclass in pairs(aClass.subclasses)do if rawget(subclass.__declaredMethods,name)==nil then _propagateInstanceMethod(subclass,name,f)end end end local function _declareInstanceMethod(aClass,name,f)aClass.__declaredMethods[name]=f if f==nil and aClass.super then f=aClass.super.__instanceDict[name] end _propagateInstanceMethod(aClass,name,f)end local function _tostring(self) return "class " .. self.name end local function _call(self,...)return self:new(...)end local function _createClass(name,super)local dict={}dict.__index=dict local aClass={ name=name,super=super,static={},__instanceDict=dict,__declaredMethods={},subclasses=setmetatable({}, {__mode='k'}) }if super then setmetatable(aClass.static,{__index=function(_,k)local result=rawget(dict,k)if result==nil then return super.static[k] end return result end })else setmetatable(aClass.static,{ __index=function(_,k)return rawget(dict,k)end })end setmetatable(aClass,{ __index=aClass.static,__tostring=_tostring,__call=_call,__newindex=_declareInstanceMethod })return aClass end local function _includeMixin(aClass,mixin)assert(type(mixin)=='table', "mixin must be a table")for name,method in pairs(mixin)do if name ~= "included" and name ~= "static" then aClass[name] = method end end for name,method in pairs(mixin.static or {})do aClass.static[name]=method end if type(mixin.included)=="function" then mixin:included(aClass) end return aClass end local DefaultMixin={__tostring=function(self) return "instance of " .. tostring(self.class) end,__gc=function(self)if type(self) == 'table' and type(self.class) == 'table' and type(self.class.finalize) == 'function' then self:finalize()end end,initialize=function(self,...)end,isInstanceOf=function(self,aClass)return type(aClass)=='table'and type(self)=='table'and(self.class==aClass or type(self.class)=='table'and type(self.class.isSubclassOf)=='function'and self.class:isSubclassOf(aClass))end,static={allocate=function(self)assert(type(self)=='table', "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'")local instance=setmetatable({ class=self },self.__instanceDict)if instance.__allocate then instance:__allocate()end return instance end,new=function(self,...)assert(type(self)=='table', "Make sure that you are using 'Class:new' instead of 'Class.new'")local instance=self:allocate()instance:initialize(...)return instance end,subclass=function(self,name)assert(type(self)=='table', "Make sure that you are using 'Class:subclass' instead of 'Class.subclass'")assert(type(name)=="string", "You must provide a name(string) for your class")local subclass=_createClass(name,self)for methodName,f in pairs(self.__instanceDict)do _propagateInstanceMethod(subclass,methodName,f)end subclass.initialize=function(instance,...)return self.initialize(instance,...)end self.subclasses[subclass]=true self:subclassed(subclass)return subclass end,subclassed=function(self,other)end,isSubclassOf=function(self,other)return type(other)=='table' and type(self.super)=='table' and (self.super==other or self.super:isSubclassOf(other))end,include=function(self,...)assert(type(self)=='table', "Make sure you that you are using 'Class:include' instead of 'Class.include'")for _,mixin in ipairs({...})do _includeMixin(self,mixin)end return self end }}function middleclass.class(name,super)assert(type(name)=='string', "A name (string) is needed for the new class")return super and super:subclass(name)or _includeMixin(_createClass(name),DefaultMixin)end setmetatable(middleclass,{ __call=function(_,...)return middleclass.class(...)end })return middleclass end)() local class=__mod_rtk_middleclass rtk.Attribute={FUNCTION={},NIL={},DEFAULT={},default=nil,type=nil,calculate=nil,priority=nil,reflow=nil,redraw=nil,replaces=nil,animate=nil,get=nil,set=nil,}setmetatable(rtk.Attribute,{__call=function(self,attrs)attrs._is_rtk_attr=true return attrs end })local falsemap={[false]=true,[0]=true,['0']=true,['false']=true,['False']=true,['FALSE']=true }local typemaps={number=function(v)local n=tonumber(v)if n then return n elseif v == 'true' or v == true then return 1 elseif v == 'false' or v == false then return 0 end end,string=tostring,boolean=function(v)if falsemap[v] then return false elseif v then return true end end,}function rtk.Reference(attr)return {_is_rtk_reference=true,attr=attr }end local function register(cls,attrs)local attributes=cls.static.attributes if attributes and attributes.__class==cls.name then elseif cls.super then attributes={}for k,v in pairs(cls.super.static.attributes)do if k ~= '__class' and k ~= 'get' then attributes[k]=table.shallow_copy(v)end end else attributes={defaults={}}end local refs={}for attr,attrtable in pairs(attrs)do assert(attr ~= 'id' and attr ~= 'get' and attr ~= 'defaults',"attempted to assign a reserved attribute")if type(attrtable)=='table' and attrtable._is_rtk_reference then local srcattr=attrtable.attr attrtable={}refs[#refs+1]={attrtable,nil,srcattr,attr}else if type(attrtable) ~='table' or not attrtable._is_rtk_attr then attrtable={default=attrtable}end if attributes[attr] then attrtable=table.merge(attributes[attr],attrtable)end for field,v in pairs(attrtable)do if type(v)=='table' and v._is_rtk_reference then refs[#refs+1]={attrtable,field,v.attr,attr}end end local deftype=type(attrtable.default)if deftype=='function' then attrtable.default_func=attrtable.default attrtable.default=rtk.Attribute.FUNCTION end if (not attrtable.type and not attrtable.calculate) or type(attrtable.type)=='string' then attrtable.type=typemaps[attrtable.type or deftype] end end attributes[attr]=attrtable attributes.defaults[attr]=attrtable.default end for _,ref in ipairs(refs)do local attrtable,field,srcattr,dstattr=table.unpack(ref)local src=attributes[srcattr] if not attributes.defaults[dstattr] and not field then attributes.defaults[dstattr]=attributes.defaults[srcattr] end if field then attrtable[field]=src[field] else for k,v in pairs(src)do attrtable[k]=v end end end attributes.__class=cls.name attributes.get=function(attr)return attributes[attr] or rtk.Attribute.NIL end cls.static.attributes=attributes end function rtk.class(name,super,attributes)local cls=class(name,super)cls.static.register=function(attrs)register(cls,attrs)end if attributes then register(cls,attributes)end return cls end function rtk.isa(v,cls)if type(v)=='table' and v.isInstanceOf then return v:isInstanceOf(cls)end return false end end)() __mod_rtk_utils=(function() local rtk=__mod_rtk_core rtk.file={}rtk.clipboard={}rtk.gfx={}UNDO_STATE_ALL=-1 UNDO_STATE_TRACKCFG=1 UNDO_STATE_FX=2 UNDO_STATE_ITEMS=4 UNDO_STATE_MISCCFG=8 UNDO_STATE_FREEZE=16 UNDO_STATE_TRACKENV=32 UNDO_STATE_FXENV=64 UNDO_STATE_POOLEDENVS=128 UNDO_STATE_FX_ARA=256 function rtk.check_reaper_version(major,minor,exact)local curmaj=rtk._reaper_version_major local curmin=rtk._reaper_version_minor minor=minor<100 and minor or minor/10 if exact then return curmaj==major and curmin==minor else return(curmaj>major)or(curmaj==major and curmin>=minor)end end function rtk.clamp(value,min,max)if min and max then return math.max(min,math.min(max,value))elseif min then return math.max(min,value)elseif max then return math.min(max,value)else return value end end function rtk.clamprel(value,min,max)min=min and min<1.0 and min*value or min max=max and max<1.0 and max*value or max if min and max then return math.max(min,math.min(max,value))elseif min then return math.max(min,value)elseif max then return math.min(max,value)else return value end end function rtk.isrel(value)return value and value>0 and value<=1.0 end function rtk.point_in_box(x,y,bx,by,bw,bh)return x>=bx and y>=by and x<=bx+bw and y<=by+bh end function rtk.point_in_circle(x,y,cirx,ciry,radius)local dx=x-cirx local dy=y-ciry return dx*dx+dy*dy<=radius*radius end function rtk.open_url(url)if rtk.os.windows then reaper.ExecProcess(string.format('cmd.exe /C start /B "" "%s"', url), -2)elseif rtk.os.mac then os.execute(string.format('open "%s"', url))elseif rtk.os.linux then reaper.ExecProcess(string.format('xdg-open "%s"', url), -2)else reaper.ShowMessageBox("Sorry, I don't know how to open URLs on this operating system.","Unsupported operating system", 0 )end end function rtk.uuid4()return reaper.genGuid():sub(2,-2):lower()end function rtk.file.read(fname)local f,err=io.open(fname)if f then local contents=f:read("*all")f:close()return contents,nil else return nil,err end end function rtk.file.write(fname,contents)local f, err=io.open(fname, "w")if f then f:write(contents)f:close()else return err end end function rtk.file.size(fname)local f,err=io.open(fname)if f then local size=f:seek("end")f:close()return size,nil else return nil,err end end function rtk.file.exists(fname)return reaper.file_exists(fname)end function rtk.clipboard.get()if not reaper.CF_GetClipboardBig then return end local fast=reaper.SNM_CreateFastString("")local data=reaper.CF_GetClipboardBig(fast)reaper.SNM_DeleteFastString(fast)return data end function rtk.clipboard.set(data)if not reaper.CF_SetClipboard then return false end reaper.CF_SetClipboard(data)return true end function rtk.gfx.roundrect(x,y,w,h,r,thickness,aa)thickness=thickness or 1 aa=aa or 1 w=w-1 h=h-1 if thickness==1 then gfx.roundrect(x,y,w,h,r,aa)elseif thickness>1 then for i=0,thickness-1 do gfx.roundrect(x+i,y+i,w-i*2,h-i*2,r,aa)end elseif h>=2*r then gfx.circle(x+r,y+r,r,1,aa)gfx.circle(x+w-r,y+r,r,1,aa)gfx.circle(x+r,y+h-r,r,1,aa)gfx.circle(x+w-r,y+h-r,r,1,aa)gfx.rect(x,y+r,r,h-r*2)gfx.rect(x+w-r,y+r,r+1,h-r*2)gfx.rect(x+r,y,w-r*2,h+1)else r=h/2-1 gfx.circle(x+r,y+r,r,1,aa)gfx.circle(x+w-r,y+r,r,1,aa)gfx.rect(x+r,y,w-r*2,h)end end rtk.IndexManager=rtk.class('rtk.IndexManager')function rtk.IndexManager:initialize(first,last)self.first=first self.last=last self._last=last-first self._bitmaps={}self._tail_idx=nil self._last_idx=nil end function rtk.IndexManager:_set(idx,value)local elem=math.floor(idx/32)+1 local count=#self._bitmaps if elem>count then for n=1,elem-count do self._bitmaps[#self._bitmaps+1]=0 end end local bit=idx%32 if value~=0 then self._bitmaps[elem]=self._bitmaps[elem]|(1<#self._bitmaps then return false end local bit=idx%32 return self._bitmaps[elem]&(1<=self._last%32 then return nil end idx=(elem-1)*32+bit end self._last_idx=idx self._tail_idx=self._tail_idx and math.max(self._tail_idx,idx)or idx self:_set(idx,1)return idx+self.first end function rtk.IndexManager:next(gc)local idx=self:_next()if not idx and gc then collectgarbage('collect')idx=self:_next()end return idx end function rtk.IndexManager:release(idx)self:_set(idx-self.first,0)end math.inf=1/0 function math.round(n)return n and(n%1>=0.5 and math.ceil(n)or math.floor(n))end function string.startswith(s,prefix,insensitive)if insensitive==true then return s:lower():sub(1,string.len(prefix))==prefix:lower()else return s:sub(1,string.len(prefix))==prefix end end function string.split(s,delim,filter)local parts={}for word in s:gmatch('[^' .. (delim or '%s') .. ']' .. (filter and '+' or '*')) do parts[#parts+1]=word end return parts end function string.strip(s)return s:match('^%s*(.-)%s*$')end function string.hash(s)local hash=5381 for i=1,#s do hash=((hash<<5)+hash)+s:byte(i)end return hash&0x7fffffffffffffff end function string.count(s,sub)local c=-1 local idx=0 while idx do _,idx=s:find(sub,idx+1)c=c+1 end return c end local _table_tostring=nil local function val_to_str(v,seen)if "string" == type(v) then v=string.gsub(v, "\n", "\\n")if string.match(string.gsub(v,"[^'\"]",""), '^"+$') then return "'" .. v .. "'"end return '"' .. string.gsub(v, '"', '\\"') .. '"'else if type(v)=='table' and not v.__tostring then return seen[tostring(v)] and '' or _table_tostring(v, seen)else return tostring(v)end return "table" == type(v) and _table_tostring(v, seen) or tostring(v)end end local function key_to_str(k,seen)if "string" == type(k) and string.match(k, "^[_%a][_%a%d]*$") then return k else return "[" .. val_to_str(k, seen) .. "]"end end _table_tostring=function(tbl,seen)local result,done={},{}seen=seen or {}local id=tostring(tbl)seen[id]=1 for k,v in ipairs(tbl)do table.insert(result,val_to_str(v,seen))done[k]=true end for k,v in pairs(tbl)do if not done[k] then table.insert(result, key_to_str(k, seen) .. "=" .. val_to_str(v, seen))end end seen[id]=nil return "{" .. table.concat( result, "," ) .. "}"end function table.tostring(tbl)return _table_tostring(tbl)end function table.fromstring(str)return load('return ' .. str)()end function table.merge(dst,src)for k,v in pairs(src)do dst[k]=v end return dst end function table.shallow_copy(t,merge)local copy={}for k,v in pairs(t)do copy[k]=v end if merge then table.merge(copy,merge)end return copy end function table.keys(t)local keys={}for k,_ in pairs(t)do keys[#keys+1]=k end return keys end function table.values(t)local values={}for _,v in pairs(t)do values[#values+1]=v end return values end end)() __mod_rtk_future=(function() local rtk=__mod_rtk_core rtk.Future=rtk.class('rtk.Future')rtk.Future.static.PENDING=false rtk.Future.static.DONE=true rtk.Future.static.CANCELLED=0 function rtk.Future:initialize()self.state=rtk.Future.PENDING self.result=nil self.cancellable=false end function rtk.Future:after(func)if not self._after then self._after={func}else self._after[#self._after+1]=func end self:_check_defer_resolved_callbacks(rtk.Future.DONE)return self end function rtk.Future:done(func)if not self._done then self._done={func}else self._done[#self._done+1]=func end self:_check_defer_resolved_callbacks(rtk.Future.DONE)return self end function rtk.Future:cancelled(func)self.cancellable=true if self.state==rtk.Future.CANCELLED then func(self.result)elseif not self._cancelled then self._cancelled={func}else self._cancelled[#self._cancelled+1]=func end return self end function rtk.Future:cancel(v)assert(self._cancelled, 'Future is not cancelleable')assert(self.state==rtk.Future.PENDING, 'Future has already been resolved or cancelled')self.state=rtk.Future.CANCELLED self.result=v for i=1,#self._cancelled do self._cancelled[i](v)end self._cancelled=nil return self end function rtk.Future:_resolve(value)self.result=value self:_invoke_resolved_callbacks(value)end function rtk.Future:_check_defer_resolved_callbacks(state,value)if self.state==state and not self._deferred then self._deferred=true rtk.defer(rtk.Future._invoke_resolved_callbacks,self,value or self.value)end end function rtk.Future:_invoke_resolved_callbacks(value)self._deferred=false self.result=value local nextval=value if self._after then while #self._after>0 do local func=table.remove(self._after,1)nextval=func(nextval)or nextval if rtk.isa(nextval,rtk.Future)then nextval:done(function(v)self:_resolve(v)end)self:cancelled(function(v)nextval:cancel(v)end)return end end end self.state=rtk.Future.DONE if self._done and(not self._after or #self._after==0)then for i=1,#self._done do self._done[i](nextval)end end self._done=nil self._after=nil return self end function rtk.Future:resolve(value)assert(self.state==rtk.Future.PENDING, 'Future has already been resolved or cancelled')if not self._after and not self._done and not self._deferred then self._deferred=true rtk.defer(self._resolve,self,value,true)else self:_resolve(value)end return self end end)() __mod_rtk_animate=(function() local rtk=__mod_rtk_core local log=__mod_rtk_log local c1=1.70158 local c2=c1*1.525 local c3=c1+1 local c4=(2*math.pi)/3 local c5=(2*math.pi)/4.5 local n1=7.5625 local d1=2.75 rtk.easing={['linear'] = function(x)return x end,['in-sine'] = function(x)return 1-math.cos((x*math.pi)/2)end,['out-sine'] = function(x)return math.sin((x*math.pi)/2)end,['in-out-sine'] = function(x)return-(math.cos(math.pi*x)-1)/2 end,['in-quad'] = function(x)return x*x end,['out-quad'] = function(x)return 1-(1-x)*(1-x)end,['in-out-quad'] = function(x)return(x<0.5)and(2*x*x)or(1-(-2*x+2)^2/2)end,['in-cubic'] = function(x)return x*x*x end,['out-cubic'] = function(x)return 1-(1-x)^4 end,['in-out-cubic'] = function(x)return(x<0.5)and(4*x*x*x)or(1-(-2*x+2)^3/2)end,['in-quart'] = function(x)return x*x*x*x end,['out-quart'] = function(x)return 1-(1-x)^4 end,['in-out-quart'] = function(x)return(x<0.5)and(8*x*x*x*x)or(1-(-2*x+2)^4/2)end,['in-quint'] = function(x)return x*x*x*x*x end,['out-quint'] = function(x)return 1-(1-x)^5 end,['in-out-quint'] = function(x)return(x<0.5)and(16*x*x*x*x*x)or(1-(-2*x+2)^5/2)end,['in-expo'] = function(x)return(x==0)and 0 or 2^(10*x-10)end,['out-expo'] = function(x)return(x==1)and 1 or(1-2^(-10*x))end,['in-out-expo'] = function(x)return(x==0)and 0 or (x==1)and 1 or (x<0.5)and 2^(20*x-10)/2 or(2-2^(-20*x+10))/2 end,['in-circ'] = function(x)return 1-math.sqrt(1-x^2)end,['out-circ'] = function(x)return math.sqrt(1-(x-1)^2)end,['in-out-circ'] = function(x)return(x<0.5)and(1-math.sqrt(1-(2*x)^2))/2 or(math.sqrt(1-(-2*x+2)^2)+1)/2 end,['in-back'] = function(x)return c3*x*x*x-c1*x*x end,['out-back'] = function(x)return 1+(c3*(x-1)^3)+(c1*(x-1)^2)end,['in-out-back'] = function(x)return(x<0.5)and ((2*x)^2*((c2+1)*2*x-c2))/2 or ((2*x-2)^2*((c2+1)*(x*2-2)+c2)+2)/2 end,['in-elastic'] = function(x)return(x==0)and 0 or (x==1)and 1 or -2^(10*x-10)*math.sin((x*10-10.75)*c4)end,['out-elastic'] = function(x)return(x==0)and 0 or (x==1)and 1 or 2^(-10*x)*math.sin((x*10-0.75)*c4)+1 end,['in-out-elastic'] = function(x)return(x==0)and 0 or (x==1)and 1 or (x<0.5)and-(2^(20*x-10)*math.sin((20*x-11.125)*c5))/2 or (2^(-20*x+10)*math.sin((20*x-11.125)*c5))/2+1 end,['in-bounce'] = function(x)return 1 - rtk.easing['out-bounce'](1 - x)end,['out-bounce'] = function(x)if x<1/d1 then return n1*x*x elseif x<(2/d1)then x=x-1.5/d1 return n1*x*x+0.75 elseif x<(2.5/d1)then x=x-2.25/d1 return n1*x*x+0.9375 else x=x-2.625/d1 return n1*x*x+0.984375 end end,['in-out-bounce'] = function(x)return(x<0.5)and (1 - rtk.easing['out-bounce'](1 - 2 * x)) / 2 or (1 + rtk.easing['out-bounce'](2 * x - 1)) / 2 end,}local function _resolve(x,src,dst)return src+x*(dst-src)end local _table_stepfuncs={[1]=function(widget,anim)local x=anim.easingfunc(anim.pct)return {_resolve(x,anim.src[1],anim.dst[1])}end,[2]=function(widget,anim)local x=anim.easingfunc(anim.pct)local src,dst=anim.src,anim.dst local f1=_resolve(x,src[1],dst[1])local f2=_resolve(x,src[2],dst[2])return {f1,f2}end,[3]=function(widget,anim)local x=anim.easingfunc(anim.pct)local src,dst=anim.src,anim.dst local f1=_resolve(x,src[1],dst[1])local f2=_resolve(x,src[2],dst[2])local f3=_resolve(x,src[3],dst[3])return {f1,f2,f3}end,[4]=function(widget,anim)local x=anim.easingfunc(anim.pct)local src,dst=anim.src,anim.dst local f1=_resolve(x,src[1],dst[1])local f2=_resolve(x,src[2],dst[2])local f3=_resolve(x,src[3],dst[3])local f4=_resolve(x,src[4],dst[4])return {f1,f2,f3,f4}end,any=function(widget,anim)local x=anim.easingfunc(anim.pct)local src,dst=anim.src,anim.dst local result={}for i=1,#src do result[i]=_resolve(x,src[i],dst[i])end return result end }function rtk._do_animations(now)if not rtk._frame_times then rtk._frame_times={now}else local times=rtk._frame_times local c=#times times[c+1]=now if c>30 then table.remove(times,1)end rtk.fps=c/(times[c]-times[1])end if rtk._animations_len>0 then local donefuncs=nil local done=nil for key,anim in pairs(rtk._animations)do local widget=anim.widget local target=anim.target or anim.widget local attr=anim.attr local finished=anim.pct>=1.0 local elapsed=now-anim._start_time local newval,exterior if anim.stepfunc then newval,exterior=anim.stepfunc(target,anim)else newval=anim.resolve(anim.easingfunc(anim.pct))end anim.frames=anim.frames+1 if not finished and elapsed>anim.duration*1.5 then log.warning('animation: %s %s - failed to complete within 1.5x of duration (fps: current=%s expected=%s)',target,attr,rtk.fps,anim.startfps)finished=true end if anim.update then anim.update(finished and anim.doneval or newval,target,attr,anim)end if widget then if not finished then local value=newval if exterior==nil and anim.calculate then value=anim.calculate(widget,attr,newval,widget.calc)exterior=value end widget.calc[attr]=value if anim.sync_exterior_value then widget[attr]=exterior or value end else widget:attr(attr,anim.doneval or exterior)end local reflow=anim.reflow or(anim.attrmeta and anim.attrmeta.reflow)or rtk.Widget.REFLOW_PARTIAL if reflow and reflow~=rtk.Widget.REFLOW_NONE then widget:queue_reflow(reflow)end if anim.attrmeta and anim.attrmeta.window_sync then widget._sync_window_attrs_on_update=true end end if finished then rtk._animations[key]=nil rtk._animations_len=rtk._animations_len-1 if not done then done={}end done[#done+1]=anim else anim.pct=anim.pct+anim.pctstep end end if done then for _,anim in ipairs(done)do anim.future:resolve(anim.widget or anim.target)local took=reaper.time_precise()-anim._start_time local missed=took-anim.duration log.log(math.abs(missed)>0.05 and log.DEBUG or log.DEBUG2,'animation: done %s: %s -> %s on %s frames=%s current-fps=%s expected-fps=%s took=%.1f (missed by %.3f)',anim.attr,anim.src,anim.dst,anim.target or anim.widget,anim.frames,rtk.fps,anim.startfps,took,missed )end end return true end end local function _is_equal(a,b)local ta=type(a)if ta~=type(b)then return false elseif ta=='table' then if #a~=#b then return false end for i=1,#a do if a[i]~=b[i] then return false end end return true end return a==b end function rtk.queue_animation(kwargs)assert(kwargs and kwargs.key, 'animation table missing key field')local future=rtk.Future()local key=kwargs.key local anim=rtk._animations[key] if anim then if _is_equal(anim.dst,kwargs.dst)then return anim.future else anim.future:cancel()end end if _is_equal(kwargs.src,kwargs.dst)then future:resolve()return future end future:cancelled(function()rtk._animations[key]=nil rtk._animations_len=rtk._animations_len-1 end)local duration=kwargs.duration or 0.5 local easingfunc=rtk.easing[kwargs.easing or 'linear'] assert(type(easingfunc)=='function', string.format('unknown easing function: %s', kwargs.easing))if not kwargs.stepfunc then local tp=type(kwargs.src or 0)if tp=='table' then local sz=#kwargs.src for i=1,sz do assert(type(kwargs.src[i])=='number', 'animation src value table must not have non-numeric elements')end kwargs.stepfunc=_table_stepfuncs[sz] if not kwargs.stepfunc then kwargs.stepfunc=_table_stepfuncs.any end else assert(tp=='number', string.format('animation src value %s is invalid', kwargs.src))end end if not rtk._animations[kwargs.key] then rtk._animations_len=rtk._animations_len+1 end local step=1.0/(rtk.fps*duration)anim=table.shallow_copy(kwargs,{easingfunc=easingfunc,src=kwargs.src or(not kwargs.stepfunc and 0 or nil),dst=kwargs.dst or 0,doneval=kwargs.doneval or kwargs.dst,pct=step,pctstep=step,duration=duration,future=future,frames=0,startfps=rtk.fps,_start_time=reaper.time_precise()})anim.resolve=function(x)return _resolve(x,anim.src,anim.dst)end rtk._animations[kwargs.key]=anim log.debug2('animation: scheduled %s', kwargs.key)return future end end)() __mod_rtk_color=(function() local rtk=__mod_rtk_core local log=__mod_rtk_log rtk.color={}function rtk.color.set(color,amul)local r,g,b,a=rtk.color.rgba(color)if amul then a=a*amul end gfx.set(r,g,b,a)end function rtk.color.rgba(color)local tp=type(color)if tp=='table' then local r,g,b,a=table.unpack(color)return r,g,b,a or 1 elseif tp=='string' then local hash=color:find('#')if hash==1 then return rtk.color.hex2rgba(color)else local a if hash then a=(tonumber(color:sub(hash+1),16)or 0)/255 color=color:sub(1,hash-1)end local resolved=rtk.color.names[color:lower()] if not resolved then log.warning('rtk: color "%s" is invalid, defaulting to black', color)return 0,0,0,a or 1 end local r,g,b,a2=rtk.color.hex2rgba(resolved)return r,g,b,a or a2 end elseif tp=='number' then local r,g,b=color&0xff,(color>>8)&0xff,(color>>16)&0xff return r/255,g/255,b/255,1 else error('invalid type ' .. tp .. ' passed to rtk.color.rgba()')end end function rtk.color.luma(color,under)if not color then return under and rtk.color.luma(under)or 0 end local r,g,b,a=rtk.color.rgba(color)local luma=(0.2126*r+0.7152*g+0.0722*b)if a<1.0 then luma=math.abs((luma*a)+(under and(rtk.color.luma(under)*(1-a))or 0))end return luma end function rtk.color.hsv(color)local r,g,b,a=rtk.color.rgba(color)local h,s,v local max=math.max(r,g,b)local min=math.min(r,g,b)local delta=max-min if delta==0 then h=0 elseif max==r then h=60*(((g-b)/delta)%6)elseif max==g then h=60*(((b-r)/delta)+2)elseif max==b then h=60*(((r-g)/delta)+4)end s=(max==0)and 0 or(delta/max)v=max return h/360.0,s,v,a end function rtk.color.hsl(color)local r,g,b,a=rtk.color.rgba(color)local h,s,l local max=math.max(r,g,b)local min=math.min(r,g,b)l=(max+min)/2 if max==min then h=0 s=0 else local delta=max-min if l>0.5 then s=delta/(2-max-min)else s=delta/(max+min)end if max==r then h=(g-b)/delta+(g>16)&0xff)end function rtk.color.get_reaper_theme_bg()if reaper.GetThemeColor then local r=reaper.GetThemeColor('col_tracklistbg', 0)if r~=-1 then return rtk.color.int2hex(r)end end if reaper.GSC_mainwnd then local idx=(rtk.os.mac or rtk.os.linux)and 5 or 20 return rtk.color.int2hex(reaper.GSC_mainwnd(idx))end end function rtk.color.get_icon_style(color,under)return rtk.color.luma(color, under) > rtk.light_luma_threshold and 'dark' or 'light'end function rtk.color.hex2rgba(s)local r=tonumber(s:sub(2,3),16)or 0 local g=tonumber(s:sub(4,5),16)or 0 local b=tonumber(s:sub(6,7),16)or 0 local a=tonumber(s:sub(8,9),16)return r/255,g/255,b/255,a and a/255 or 1.0 end function rtk.color.rgba2hex(r,g,b,a)r=math.ceil(r*255)b=math.ceil(b*255)g=math.ceil(g*255)if not a or a==1.0 then return string.format('#%02x%02x%02x', r, g, b)else return string.format('#%02x%02x%02x%02x', r, g, b, math.ceil(a * 255))end end function rtk.color.int2hex(n,native)if native then n=rtk.color.convert_native(n)end local r,g,b=n&0xff,(n>>8)&0xff,(n>>16)&0xff return string.format('#%02x%02x%02x', r, g, b)end function rtk.color.hsv2rgb(h,s,v,a)if s==0 then return v,v,v,a or 1.0 end local i=math.floor(h*6)local f=(h*6)-i local p=v*(1-s)local q=v*(1-s*f)local t=v*(1-s*(1-f))if i==0 or i==6 then return v,t,p,a or 1.0 elseif i==1 then return q,v,p,a or 1.0 elseif i==2 then return p,v,t,a or 1.0 elseif i==3 then return p,q,v,a or 1.0 elseif i==4 then return t,p,v,a or 1.0 elseif i==5 then return v,p,q,a or 1.0 else log.error('invalid hsv (%s %s %s) i=%s', h, s, v, i)end end local function hue2rgb(p,q,t)if t<0 then t=t+1 elseif t>1 then t=t-1 end if t<1/6 then return p+(q-p)*6*t elseif t<1/2 then return q elseif t<2/3 then return p+(q-p)*(2/3-t)*6 else return p end end function rtk.color.hsl2rgb(h,s,l,a)local r,g,b if s==0 then r,g,b=l,l,l else local q=(l<0.5)and(l*(1+s))or(l+s-l*s)local p=2*l-q r=hue2rgb(p,q,h+1/3)g=hue2rgb(p,q,h)b=hue2rgb(p,q,h-1/3)end return r,g,b,a or 1.0 end rtk.color.names={transparent="#ffffff00",black='#000000',silver='#c0c0c0',gray='#808080',white='#ffffff',maroon='#800000',red='#ff0000',purple='#800080',fuchsia='#ff00ff',green='#008000',lime='#00ff00',olive='#808000',yellow='#ffff00',navy='#000080',blue='#0000ff',teal='#008080',aqua='#00ffff',orange='#ffa500',aliceblue='#f0f8ff',antiquewhite='#faebd7',aquamarine='#7fffd4',azure='#f0ffff',beige='#f5f5dc',bisque='#ffe4c4',blanchedalmond='#ffebcd',blueviolet='#8a2be2',brown='#a52a2a',burlywood='#deb887',cadetblue='#5f9ea0',chartreuse='#7fff00',chocolate='#d2691e',coral='#ff7f50',cornflowerblue='#6495ed',cornsilk='#fff8dc',crimson='#dc143c',cyan='#00ffff',darkblue='#00008b',darkcyan='#008b8b',darkgoldenrod='#b8860b',darkgray='#a9a9a9',darkgreen='#006400',darkgrey='#a9a9a9',darkkhaki='#bdb76b',darkmagenta='#8b008b',darkolivegreen='#556b2f',darkorange='#ff8c00',darkorchid='#9932cc',darkred='#8b0000',darksalmon='#e9967a',darkseagreen='#8fbc8f',darkslateblue='#483d8b',darkslategray='#2f4f4f',darkslategrey='#2f4f4f',darkturquoise='#00ced1',darkviolet='#9400d3',deeppink='#ff1493',deepskyblue='#00bfff',dimgray='#696969',dimgrey='#696969',dodgerblue='#1e90ff',firebrick='#b22222',floralwhite='#fffaf0',forestgreen='#228b22',gainsboro='#dcdcdc',ghostwhite='#f8f8ff',gold='#ffd700',goldenrod='#daa520',greenyellow='#adff2f',grey='#808080',honeydew='#f0fff0',hotpink='#ff69b4',indianred='#cd5c5c',indigo='#4b0082',ivory='#fffff0',khaki='#f0e68c',lavender='#e6e6fa',lavenderblush='#fff0f5',lawngreen='#7cfc00',lemonchiffon='#fffacd',lightblue='#add8e6',lightcoral='#f08080',lightcyan='#e0ffff',lightgoldenrodyellow='#fafad2',lightgray='#d3d3d3',lightgreen='#90ee90',lightgrey='#d3d3d3',lightpink='#ffb6c1',lightsalmon='#ffa07a',lightseagreen='#20b2aa',lightskyblue='#87cefa',lightslategray='#778899',lightslategrey='#778899',lightsteelblue='#b0c4de',lightyellow='#ffffe0',limegreen='#32cd32',linen='#faf0e6',magenta='#ff00ff',mediumaquamarine='#66cdaa',mediumblue='#0000cd',mediumorchid='#ba55d3',mediumpurple='#9370db',mediumseagreen='#3cb371',mediumslateblue='#7b68ee',mediumspringgreen='#00fa9a',mediumturquoise='#48d1cc',mediumvioletred='#c71585',midnightblue='#191970',mintcream='#f5fffa',mistyrose='#ffe4e1',moccasin='#ffe4b5',navajowhite='#ffdead',oldlace='#fdf5e6',olivedrab='#6b8e23',orangered='#ff4500',orchid='#da70d6',palegoldenrod='#eee8aa',palegreen='#98fb98',paleturquoise='#afeeee',palevioletred='#db7093',papayawhip='#ffefd5',peachpuff='#ffdab9',peru='#cd853f',pink='#ffc0cb',plum='#dda0dd',powderblue='#b0e0e6',rosybrown='#bc8f8f',royalblue='#4169e1',saddlebrown='#8b4513',salmon='#fa8072',sandybrown='#f4a460',seagreen='#2e8b57',seashell='#fff5ee',sienna='#a0522d',skyblue='#87ceeb',slateblue='#6a5acd',slategray='#708090',slategrey='#708090',snow='#fffafa',springgreen='#00ff7f',steelblue='#4682b4',tan='#d2b48c',thistle='#d8bfd8',tomato='#ff6347',turquoise='#40e0d0',violet='#ee82ee',wheat='#f5deb3',whitesmoke='#f5f5f5',yellowgreen='#9acd32',rebeccapurple='#663399',}end)() __mod_rtk_font=(function() local rtk=__mod_rtk_core local _fontcache={}local _idmgr=rtk.IndexManager(2,127)rtk.Font=rtk.class('rtk.Font')rtk.Font.register{name=nil,size=nil,scale=nil,flags=nil,texth=nil,}function rtk.Font:initialize(name,size,scale,flags)if size then self:set(name,size,scale,flags)end end function rtk.Font:finalize()if self._idx then self:_decref()end end function rtk.Font:_decref()if not self._idx or self._idx==1 then return end local refcount=_fontcache[self._key][2] if refcount<=1 then _idmgr:release(self._idx)_fontcache[self._key]=nil else _fontcache[self._key][2]=refcount-1 end end function rtk.Font:_get_id()local idx=_idmgr:next(true)if idx then return idx end return 1 end function rtk.Font:draw(text,x,y,clipw,cliph,flags)if rtk.os.mac then local fudge=math.ceil(1*rtk.scale.value)y=y+fudge if cliph then cliph=cliph-fudge end end flags=flags or 0 self:set()if type(text)=='string' then gfx.x=x gfx.y=y if cliph then gfx.drawstr(text,flags,x+clipw,y+cliph)else gfx.drawstr(text,flags)end elseif #text==1 then local segment,sx,sy,sw,sh=table.unpack(text[1])gfx.x=x+sx gfx.y=y+sy if cliph then gfx.drawstr(segment,flags,x+clipw,y+cliph)else gfx.drawstr(segment,flags)end else flags=flags|(cliph and 0 or 256)local checkh=cliph clipw=x+(clipw or 0)cliph=y+(cliph or 0)for n=1,#text do local segment,sx,sy,sw,sh=table.unpack(text[n])local offy=y+sy if checkh and offy>cliph then break elseif offy+sh>=0 then gfx.x=x+sx gfx.y=offy gfx.drawstr(segment,flags,clipw,cliph)end end end end function rtk.Font:measure(s)self:set()return gfx.measurestr(s)end local _wrap_characters={[' '] = true,['-'] = true,[','] = true,['.'] = true,['!'] = true,['?'] = true,['\n'] = true,['/'] = true,['\\'] = true,[';'] = true,[':'] = true,}function rtk.Font:layout(text,boxw,boxh,wrap,align,relative,spacing,breakword)self:set()local segments={text=text,boxw=boxw,boxh=boxh,wrap=wrap,align=align,relative=relative,spacing=spacing,multiplier=rtk.font.multiplier,scale=rtk.scale.value,dirty=false,isvalid=function()return not self.dirty and self.scale==rtk.scale.value and self.multiplier==rtk.font.multiplier end }align=align or rtk.Widget.LEFT spacing=(spacing or 0)+math.ceil((rtk.os.mac and 3 or 0)*rtk.scale.value)if not text:find('\n') then local w,h=gfx.measurestr(text)if w<=boxw or not wrap then segments[1]={text,0,0,w,h}return segments,w,h end end local maxwidth=0 local y=0 local function addsegment(segment)local w,h=gfx.measurestr(segment)segments[#segments+1]={segment,0,y,w,h}maxwidth=math.max(w,maxwidth)y=y+h+spacing end if not wrap then for n, line in ipairs(text:split('\n')) do if #line>0 then addsegment(line)else y=y+self.texth+spacing end end else local startpos=1 local wrappos=1 local len=text:len()for endpos=1,len do local substr=text:sub(startpos,endpos)local ch=text:sub(endpos,endpos)local w,h=gfx.measurestr(substr)if _wrap_characters[ch] then wrappos=endpos end if w > boxw or ch=='\n' then local wrapchar=_wrap_characters[text:sub(wrappos,wrappos)] if breakword and(wrappos==startpos or not wrapchar)then wrappos=endpos-1 end if wrappos>startpos and(breakword or wrapchar)then addsegment(text:sub(startpos,wrappos):strip())startpos=wrappos+1 wrappos=endpos elseif ch=='\n' then y=y+self.texth+spacing end end end if startpos<=len then addsegment(string.strip(text:sub(startpos,len)))end end if align==rtk.Widget.CENTER then maxwidth=relative and maxwidth or boxw for n,segment in ipairs(segments)do segment[2]=(maxwidth-segment[4])/2 end end if align==rtk.Widget.RIGHT then maxwidth=relative and maxwidth or boxw for n,segment in ipairs(segments)do segment[2]=maxwidth-segment[4] end end return segments,maxwidth,y end function rtk.Font:set(name,size,scale,flags)local global_scale=rtk.scale.value if not size and self._last_global_scale~=global_scale then name=name or self.name size=self.size scale=scale or self.scale flags=flags or self.flags else scale=scale or 1 flags=flags or 0 end local sz=size and math.ceil(size*scale*global_scale*rtk.font.multiplier)local newfont=name and(name~=self.name or sz~=self.calcsize or flags~=self.flags)if self._idx and self._idx>1 then if not newfont then gfx.setfont(self._idx)return false else self:_decref()end elseif self._idx==1 then gfx.setfont(1,self.name,self.calcsize,self.flags)return true end if not newfont then error('rtk.Font:set() called without arguments and no font parameters previously set')end local key=name..tostring(sz)..tostring(flags)local cache=_fontcache[key] local idx if not cache then idx=self:_get_id()if idx>1 then _fontcache[key]={idx,1}end else cache[2]=cache[2]+1 idx=cache[1] end gfx.setfont(idx,name,sz,flags)self._key=key self._idx=idx self._last_global_scale=global_scale self.name=name self.size=size self.scale=scale self.flags=flags self.calcsize=sz self.texth=gfx.texth return true end end)() __mod_rtk_event=(function() local rtk=__mod_rtk_core local log=__mod_rtk_log rtk.Event=rtk.class('rtk.Event')rtk.Event.static.MOUSEDOWN=1 rtk.Event.static.MOUSEUP=2 rtk.Event.static.MOUSEMOVE=3 rtk.Event.static.MOUSEWHEEL=4 rtk.Event.static.KEY=5 rtk.Event.static.DROPFILE=6 rtk.Event.static.WINDOWCLOSE=7 rtk.Event.static.typenames={[rtk.Event.MOUSEDOWN]='mousedown',[rtk.Event.MOUSEUP]='mouseup',[rtk.Event.MOUSEMOVE]='mousemove',[rtk.Event.MOUSEWHEEL]='mousewheel',[rtk.Event.KEY]='key',[rtk.Event.DROPFILE]='dropfile',[rtk.Event.WINDOWCLOSE]='windowclose',}rtk.Event.register{type=nil,handled=nil,button=0,buttons=0,wheel=0,hwheel=0,char=nil,keycode=nil,keynorm=nil,ctrl=false,shift=false,alt=false,meta=false,modifiers=nil,files=nil,x=nil,y=nil,time=0,tick=nil,simulated=nil,debug=nil,}function rtk.Event:initialize(attrs)self:reset()if attrs then table.merge(self,attrs)end end function rtk.Event:__tostring()local custom if self.type>=1 and self.type<=3 then custom = string.format(' button=%s buttons=%s', self.button, self.buttons)elseif self.type==4 then custom = string.format(' wheel=%s,%s', self.hwheel, self.wheel)elseif self.type==5 then custom = string.format(' char=%s keycode=%s', self.char, self.keycode)elseif self.type==6 then custom=' ' .. table.tostring(self.files)end return string.format('Event<%s xy=%s,%s handled=%s sim=%s%s>',rtk.Event.typenames[self.type] or 'unknown',self.x,self.y,self.handled,self.simulated,custom or '')end function rtk.Event:reset(type)table.merge(self,self.class.attributes.defaults)self.type=type self.handled=nil self.debug=nil self.files=nil self.simulated=nil self.time=nil self.char=nil self.x=gfx.mouse_x self.y=gfx.mouse_y self.tick=rtk.tick return self end function rtk.Event:is_mouse_event()return self.type<=rtk.Event.MOUSEWHEEL end function rtk.Event:get_button_duration(button)local buttonstate=rtk.mouse.state[button or self.button] if buttonstate then return self.time-buttonstate.time end end function rtk.Event:set_widget_mouseover(widget)if rtk.debug and not self.debug then self.debug=widget end if widget.calc.tooltip and not rtk._mouseover_widget and self.type==rtk.Event.MOUSEMOVE and not self.simulated then rtk._mouseover_widget=widget end end function rtk.Event:set_widget_pressed(widget)if not rtk._pressed_widgets then rtk._pressed_widgets={order={}}end table.insert(rtk._pressed_widgets.order,widget)rtk._pressed_widgets[widget.id]={self.x,self.y,self.time}if not rtk._drag_candidates then rtk._drag_candidates={}end table.insert(rtk._drag_candidates,{widget,false})end function rtk.Event:is_widget_pressed(widget)return rtk._pressed_widgets and rtk._pressed_widgets[widget.id] and true or false end function rtk.Event:set_button_state(key,value)rtk.mouse.state[self.button][key]=value end function rtk.Event:get_button_state(key)local s=rtk.mouse.state[self.button] return s and s[key] end function rtk.Event:set_modifiers(cap,button)self.modifiers=cap&(4|8|16|32)self.ctrl=cap&4~=0 self.shift=cap&8~=0 self.alt=cap&16~=0 self.meta=cap&32~=0 self.buttons=cap&(1|2|64)self.button=button end local keynorm_map={[33]=49,[64]=50,[35]=51,[36]=52,[37]=53,[94]=54,[38]=55,[42]=56,[40]=57,[41]=48,[126]=96,[95]=45,[43]=61,[123]=91,[125]=93,[58]=59,[34]=39,[60]=44,[62]=46,[63]=47,}function rtk.Event:set_keycode(keycode)self.keycode=math.ceil(keycode)self.keynorm=keycode if keycode<=26 and self.ctrl then self.keynorm=keycode+96 self.char=string.char(self.keynorm)elseif keycode>=65 and keycode<=90 then self.keynorm=keycode+32 self.char=string.char(keycode)elseif keycode>=32 and keycode~=127 then if keycode<=255 then self.keynorm=keynorm_map[keycode] or self.keycode self.char=string.char(self.keycode)elseif keycode<=282 then self.keynorm=keycode-160 self.char=string.char(self.keynorm)elseif keycode<=346 then self.keynorm=keycode-224 self.char=string.char(self.keynorm)end end end function rtk.Event:set_handled(widget)self.handled=widget or true end function rtk.Event:clone(overrides)local event=rtk.Event()for k,v in pairs(self)do event[k]=v end event.handled=nil event.tick=rtk.tick table.merge(event,overrides or {})return event end end)() __mod_rtk_image=(function() local rtk=__mod_rtk_core local log=__mod_rtk_log rtk.Image=rtk.class('rtk.Image')rtk.Image.static._icons={}rtk.Image.static.DEFAULT=0 rtk.Image.static.ADDITIVE_BLEND=1 rtk.Image.static.SUBTRACTIVE_BLEND=128 rtk.Image.static.NO_SOURCE_ALPHA=2 rtk.Image.static.NO_FILTERING=4 rtk.Image.static.FAST_BLIT=2|4 rtk.Image.static.ids=rtk.IndexManager(0,1023)local function _search_image_paths_list(id,fname,paths)if not paths or #paths==0 then return end local path=paths[1]..fname local r=gfx.loadimg(id,path)if r~=-1 then return path end if #paths>1 then for i=2,#paths do path=paths[i]..fname r=gfx.loadimg(id,path)if r~=-1 then return path end end end end function rtk.Image.static._search_image_paths_nostyle(id,fname)local path=_search_image_paths_list(id,fname,rtk._image_paths.nostyle)return path or _search_image_paths_list(id,fname,rtk._image_paths.fallback)end function rtk.Image.static._search_image_paths_style(id,fname,style)local path=_search_image_paths_list(id,fname,rtk._image_paths[style])if path then return path,style end end function rtk.Image.static._search_image_paths(id,fname,style)local path,gotstyle if not style then path,gotstyle=rtk.Image._search_image_paths_nostyle(id,fname)if not path then style=rtk.theme.iconstyle path,gotstyle=rtk.Image._search_image_paths_style(id,fname,style)end else path,gotstyle=rtk.Image._search_image_paths_style(id,fname,style)if not path then path,gotstyle=rtk.Image._search_image_paths_nostyle(id,fname)end end if not path then local other=(style=='light') and 'dark' or 'light'path,gotstyle=rtk.Image._search_image_paths_style(id,fname,other)end return path,gotstyle end function rtk.Image.static.icon(name,style)style=style or rtk.theme.iconstyle local pack=rtk.Image._icons[name] if pack then local img=pack:get(name,style)if img then return img end end if not name:find('%.[%w_]+$') then name=name .. '.png'end local img,gotstyle=rtk.Image():_load(name,style)if img then if gotstyle and gotstyle~=style then img:recolor(style=='light' and '#ffffff' or '#000000')end img.style=style end if not img then log.error('rtk: rtk.Image.icon("%s"): icon could not be loaded from any image path', name)end return img end rtk.Image.static.make_icon=rtk.Image.static.icon function rtk.Image.static.make_placeholder_icon(w,h,style)local img=rtk.Image(w or 24,h or 24)img:pushdest()rtk.color.set({1,0.2,0.2,1})gfx.setfont(1, 'Sans', w or 24)gfx.x,gfx.y=5,0 gfx.drawstr('?')img:popdest()img.style=style or 'dark'return img end rtk.Image.register{x=0,y=0,w=nil,h=nil,density=1.0,path=nil,rotation=0,id=nil,}function rtk.Image:initialize(w,h,density)table.merge(self,self.class.attributes.defaults)if h then self:create(w,h,density)end end function rtk.Image:finalize()if self.id and not self._ref then gfx.setimgdim(self.id,0,0)rtk.Image.static.ids:release(self.id)end end function rtk.Image:__tostring()local clsname=self.class.name:gsub('rtk.', '')return string.format('<%s %s,%s %sx%s id=%s density=%s path=%s ref=%s>',clsname,self.x,self.y,self.w,self.h,self.id,self.density,self.path,self._ref )end function rtk.Image:create(w,h,density)if not self.id then self.id=rtk.Image.static.ids:next(true)if not self.id then error("unable to allocate image: ran out of available REAPER image buffers")end end if h~=nil then self:resize(w,h,false)end self.density=density or 1.0 return self end function rtk.Image:load(path,density)local ok,gotstyle=self:_load(path,nil,density)if ok then return self else log.warning('rtk: rtk.Image:load("%s"): no such file found in any search paths', path)end end function rtk.Image:_load(fname,style,density)local id=self.id if not id or self._ref then id=rtk.Image.static.ids:next()end local path,gotstyle=rtk.Image._search_image_paths(id,fname,style)if path then self.id=id self.path=path self.w,self.h=gfx.getimgdim(id)self.density=density or 1.0 return self,gotstyle else rtk.Image.static.ids:release(id)self.w,self.h=nil,nil self.id=nil end end function rtk.Image:pushdest()assert(self.id, 'create() or load() must be called first')rtk.pushdest(self.id)end function rtk.Image:popdest()assert(gfx.dest==self.id, 'rtk.Image.popdest() called on image that is not the current drawing target')rtk.popdest()end function rtk.Image:clone()local newimg=rtk.Image(self.w,self.h)if self.id then newimg:blit{src=self,sx=self.x,sy=self.y}end newimg.density=self.density return newimg end function rtk.Image:resize(w,h,clear)w=math.ceil(w)h=math.ceil(h)if self.w~=w or self.h~=h then if not self.id then return self:create(w,h)end self.w,self.h=w,h gfx.setimgdim(self.id,0,0)gfx.setimgdim(self.id,w,h)end if clear~=false then self:clear()end return self end function rtk.Image:scale(w,h,mode,density)assert(w or h, 'one or both of w or h parameters must be specified')if not self.id then return rtk.Image(w,h)end local aspect=self.w/self.h w=w or(h/aspect)h=h or(w*aspect)local newimg=rtk.Image(w,h)newimg:blit{src=self,sx=self.x,sy=self.y,sw=self.w,sh=self.h,dw=newimg.w,dh=newimg.h,mode=mode}newimg.density=density or self.density return newimg end function rtk.Image:clear(color)self:pushdest()if not color then gfx.set(0,0,0,0,rtk.Image.DEFAULT,self.id,0)gfx.setimgdim(self.id,0,0)gfx.setimgdim(self.id,self.w,self.h)else rtk.color.set(color)gfx.mode=rtk.Image.DEFAULT end gfx.rect(self.x,self.y,self.w,self.h,1)gfx.set(0,0,0,1,rtk.Image.DEFAULT,self.id,1)self:popdest()return self end function rtk.Image:viewport(x,y,w,h,density)local new=rtk.Image()new.id=self.id new.density=density or self.density new.path=self.path new.x=x or 0 new.y=y or 0 new.w=w or(self.w-new.x)new.h=h or(self.h-new.y)new._ref=self return new end function rtk.Image:draw(dx,dy,a,scale,clipw,cliph,mode)return self:blit{dx=dx,dy=dy,alpha=a,clipw=clipw,cliph=cliph,mode=mode,scale=scale }end function rtk.Image:blit(attrs)attrs=attrs or {}gfx.a=attrs.alpha or 1.0 local mode=attrs.mode or rtk.Image.DEFAULT if mode&rtk.Image.SUBTRACTIVE_BLEND~=0 then mode=(mode&~rtk.Image.SUBTRACTIVE_BLEND)|rtk.Image.ADDITIVE_BLEND gfx.a=-gfx.a end gfx.mode=mode local src=attrs.src if src and type(src)=='table' then assert(rtk.isa(src, rtk.Image), 'src must be an rtk.Image or numeric image id')src=src.id end if src then self:pushdest()end local scale=(attrs.scale or 1.0)/self.density local sx=attrs.sx or self.x local sy=attrs.sy or self.y local sw=attrs.sw or self.w local sh=attrs.sh or self.h local dx=attrs.dx or 0 local dy=attrs.dy or 0 local dw=attrs.dw or(sw*scale)local dh=attrs.dh or(sh*scale)local rotation=attrs.rotation and math.rad(attrs.rotation)or self._rotation_rads if attrs.clipw and dw>attrs.clipw then sw=sw-(dw-attrs.clipw)/(dw/sw)dw=attrs.clipw end if attrs.cliph and dh>attrs.cliph then sh=sh-(dh-attrs.cliph)/(dh/sh)dh=attrs.cliph end if rotation==0 or not rotation then gfx.blit(src or self.id,1.0,0,sx,sy,sw,sh,dx or 0,dy or 0,dw,dh,0,0)else gfx.blit(src or self.id,1.0,rotation,sx-(self._soffx or 0),sy-(self._soffy or 0),self._dw,self._dh,dx-(self._doffx or 0),dy-(self._doffy or 0),self._dw,self._dh,0,0 )end gfx.mode=0 if src then self:popdest()end return self end function rtk.Image:recolor(color)local r,g,b,_=rtk.color.rgba(color)return self:filter(0,0,0,1.0,r,g,b,0)end function rtk.Image:filter(mr,mg,mb,ma,ar,ag,ab,aa)self:pushdest()gfx.muladdrect(self.x,self.y,self.w,self.h,mr,mg,mb,ma,ar,ag,ab,aa)self:popdest()return self end function rtk.Image:rect(color,x,y,w,h,fill)self:pushdest()rtk.color.set(color)gfx.rect(x,y,w,h,fill)self:popdest()return self end function rtk.Image:blur(strength,x,y,w,h)if not self.w then return self end self:pushdest()gfx.mode=6 x=x or 0 y=y or 0 for i=1,strength or 20 do gfx.x=x gfx.y=y gfx.blurto(x+(w or self.w),y+(h or self.h))end self:popdest()return self end function rtk.Image:flip_vertical()self:pushdest()gfx.mode=6 gfx.a=1 gfx.transformblit(self.id,self.x,self.y,self.w,self.h,2,2,{self.x,self.y+self.h,self.x+self.w,self.y+self.h,self.x,self.y,self.x+self.w,self.y })rtk.popdest()return self end local function _xlate(x,y,theta)return x*math.cos(theta)-y*math.sin(theta),x*math.sin(theta)+y*math.cos(theta)end function rtk.Image:rotate(degrees)self.rotation=degrees local rads=math.rad(degrees)self._rotation_rads=rads local x1,y1=0,0 local xt1,yt1=_xlate(x1,y1,rads)local x2,y2=0+self.w,0 local xt2,yt2=_xlate(x2,y2,rads)local x3,y3=0,self.h local xt3,yt3=_xlate(x3,y3,rads)local x4,y4=0+self.w,self.h local xt4,yt4=_xlate(x4,y4,rads)local xmin=math.min(xt1,xt2,xt3,xt4)local xmax=math.max(xt1,xt2,xt3,xt4)local ymin=math.min(yt1,yt2,yt3,yt4)local ymax=math.max(yt1,yt2,yt3,yt4)local dw=xmax-xmin local dh=ymax-ymin local dmax=math.max(dw,dh)self._dw=dmax self._dh=dmax self._soffx=(dmax-self.w)/2 self._soffy=(dmax-self.h)/2 self._doffx=math.max(0,(dh-dw)/2)self._doffy=math.max(0,(dw-dh)/2)return self end function rtk.Image:refresh_scale()end end)() __mod_rtk_multiimage=(function() local rtk=__mod_rtk_core local log=__mod_rtk_log rtk.MultiImage=rtk.class('rtk.MultiImage', rtk.Image)function rtk.MultiImage:initialize(...)rtk.Image.initialize(self)self._variants={}local images={...}for _,img in ipairs(images)do self:add(img)end end function rtk.MultiImage:finalize()end function rtk.MultiImage:add(path_or_image,density)local img if rtk.isa(path_or_image,rtk.Image)then assert(not rtk.isa(path_or_image, rtk.MultiImage), 'cannot add an rtk.MultiImage to an rtk.MultiImage')img=path_or_image else assert(density, 'density must be supplied when path is passed to add()')img=rtk.Image:load(path_or_image,density)end assert(not self._variants[img.density], 'replacing existing density not supported')self._variants[img.density]=img if not self.id or self.density==img.density then self:_set(img)end if not self._max or img.density>self._max.density then self._max=img end return img end function rtk.MultiImage:load(path,density)if self:add(path,density)then return self end end function rtk.MultiImage:_set(img)self.current=img self.id=img.id self.x=img.x self.y=img.y self.w=img.w self.h=img.h self.density=img.density self.path=img.path self.rotation=img.rotation end function rtk.MultiImage:refresh_scale(scale)local best=self._max scale=scale or rtk.scale.value for density,img in pairs(self._variants)do if density==scale then best=img break elseif density>scale and density 0, 'no strips provided (either as a "strips" field or as positional elements elements)')local src_idx=#self._sources+1 self._sources[src_idx]={src=attrs.src,recolors={}}local y=0 for _,strip in ipairs(strips)do assert(type(strip)=='table', 'ImagePack strip definition must be a table')assert(type(strip.w) == 'number' or type(strip.h) == 'number', 'ImagePack strip requires either "w" or "h" fields')local names=strip.names or attrs.names assert(type(names)=='table', 'ImagePack strip missing "names" field or is not table')local sizes=strip.sizes if not sizes then local density=strip.density or attrs.density or 1 if strip.size then sizes={{strip.size,density}}elseif attrs.sizes then sizes=attrs.sizes elseif attrs.size then sizes={{attrs.size,density}}else sizes={{self.default_size,density}}end end strip.w=strip.w or strip.h strip.h=strip.h or strip.w local columns=strip.columns or attrs.columns local rowwidth=columns and(columns*strip.w)local style=strip.style or attrs.style local x=0 for _,name in ipairs(names)do local subregion={id=self._last_id,src_idx=src_idx,x=x,y=y,w=strip.w,h=strip.h,}self._last_id=self._last_id+1 for _,sizedensity in ipairs(sizes)do local size,density=table.unpack(sizedensity)local key=string.format('%s:%s:%s', style, name, size)local densities=self._regions[key] if not densities then densities={}self._regions[key]=densities elseif densities[density] then error(string.format('duplicate image name "%s" for style=%s size=%s density=%s',name,style,size,density ))end densities[density]=subregion end x=x+strip.w if rowwidth and x>=rowwidth then x=0 y=y+strip.h end end y=y+strip.h end return self end function rtk.ImagePack:_get_densities(name,style)local key if not name:find(':') then key=string.format('%s:%s:%s', style, name, self.default_size)else key=string.format('%s:%s', style, name)end return key,self._regions[key] end function rtk.ImagePack:get(name,style)if not name then return end local key,densities=self:_get_densities(name,style)local multi=self._cache[key] if multi then return multi end local recolor=false if not densities and not style then style=rtk.theme.iconstyle densities=self:_get_densities(name,style)end if not densities and style then local otherstyle=style=='light' and 'dark' or 'light'recolor=true _,densities=self:_get_densities(name,otherstyle)if not densities then _,densities=self:_get_densities(name,nil)recolor=false end end if not densities then return end local multi=rtk.MultiImage()for density,region in pairs(densities)do local src=self._sources[region.src_idx] local img=src.img if not img then img=rtk.Image():load(src.src)src.img=img end if recolor then img=src.recolors[style] if not img then img=src.img:clone():recolor(style=='light' and '#ffffff' or '#000000')src.recolors[style]=img end end assert(img, string.format('could not read "%s"', src.src))multi:add(img:viewport(region.x,region.y,region.w,region.h,density))end multi.style=style self._cache[key]=multi return multi end function rtk.ImagePack:register_as_icons()local default_size=self.default_size for key,_ in pairs(self._regions)do local idx=key:find(':')local name=key:sub(idx+1)rtk.Image._icons[name]=self idx=name:find(':')local size=name:sub(idx+1)if size==default_size then name=name:sub(1,idx-1)rtk.Image._icons[name]=self end end return self end end)() __mod_rtk_shadow=(function() local rtk=__mod_rtk_core rtk.Shadow=rtk.class('rtk.Shadow')rtk.Shadow.static.RECTANGLE=0 rtk.Shadow.static.CIRCLE=1 rtk.Shadow.register{type=nil,color='#00000055',w=nil,h=nil,radius=nil,elevation=nil,}function rtk.Shadow:initialize(color)self.color=color or self.class.attributes.color.default self._image=nil self._last_draw_params=nil end function rtk.Shadow:set_rectangle(w,h,elevation,t,r,b,l)self.type=rtk.Shadow.RECTANGLE self.w=w self.h=h self.tt=t or elevation self.tr=r or elevation self.tb=b or elevation self.tl=l or elevation assert(self.tt or self.tr or self.tb or self.tl, 'missing elevation for at least one edge')self.elevation=elevation or math.max(self.tt,self.tr,self.tb,self.tl)self.radius=nil self._check_generate=true end function rtk.Shadow:set_circle(radius,elevation)self.type=rtk.Shadow.CIRCLE elevation=elevation or radius/1.5 if self.radius==radius and self.elevation==elevation then return end self.radius=radius self.elevation=elevation self._check_generate=true end function rtk.Shadow:draw(x,y,alpha)if self.radius then self:_draw_circle(x,y,alpha or 1.0)else self:_draw_rectangle(x,y,alpha or 1.0)end end function rtk.Shadow:_needs_generate()if self._check_generate==false then return false end local params=self._last_draw_params local gen=not params or self.w~=params.w or self.h~=params.h or self.tt~=params.tt or self.tr~=params.tr or self.tb~=params.tb or self.tl~=params.tl or self.elevation~=params.elevation or self.radius~=params.radius if gen then self._last_draw_params={w=self.w,h=self.h,tt=self.tt,tr=self.tr,tb=self.tb,tl=self.tl,elevation=self.elevation,radius=self.radius }end self._check_generate=false return gen end function rtk.Shadow:_draw_circle(x,y,alpha)local pad=self.elevation*3 if self:_needs_generate()then local radius=math.ceil(self.radius)local sz=(radius+2+pad)*2 if not self._image then self._image=rtk.Image(sz,sz)else self._image:resize(sz,sz,true)end self._image:pushdest()rtk.color.set(self.color)local a=0.65-0.5*(1-1/self.elevation)local inflection=radius local origin=-math.log(1/(pad))for i=radius+pad,1,-1 do if i>inflection then gfx.a2=-math.log((i-inflection)/(pad))/origin*a else end gfx.circle(pad+radius,pad+radius,i,1,1)end gfx.a2=1 gfx.set(0,0,0,1)self._image:popdest()self._needs_draw=false end self._image:draw(x-pad,y-pad,alpha)end function rtk.Shadow:_draw_rectangle(x,y,alpha)local tt,tr,tb,tl=self.tt,self.tr,self.tb,self.tl local pad=math.max(tl,tr,tt,tb)if self:_needs_generate()then local w=self.w+(tl+tr)+pad*2 local h=self.h+(tt+tb)+pad*2 if not self._image then self._image=rtk.Image(w,h)else self._image:resize(w,h,true)end self._image:pushdest()rtk.color.set(self.color)local a=gfx.a gfx.a=1 for i=0,pad do gfx.a2=a*(i+1)/pad rtk.gfx.roundrect(pad+i,pad+i,self.w+tl+tr-i*2,self.h+tt+tb-i*2,self.elevation,0)end self._image:popdest()self._needs_draw=false end if tr>0 then self._image:blit{sx=pad+tl+self.w,sw=tr+pad,sh=nil,dx=x+self.w,dy=y-tt-pad,alpha=alpha }end if tb>0 then self._image:blit{sy=pad+tt+self.h,sw=self.w+tl+pad,sh=tb+pad,dx=x-tl-pad,dy=y+self.h,alpha=alpha }end if tt>0 then self._image:blit{sx=0,sy=0,sw=self.w+tl+pad,sh=pad+tt,dx=x-tl-pad,dy=y-tt-pad,alpha=alpha }end if tl>0 then self._image:blit{sx=0,sy=pad+tt,sw=pad+tl,sh=self.h,dx=x-tl-pad,dy=y,alpha=alpha }end end end)() __mod_rtk_nativemenu=(function() local rtk=__mod_rtk_core rtk.NativeMenu=rtk.class('rtk.NativeMenu')rtk.NativeMenu.static.SEPARATOR=0 function rtk.NativeMenu:initialize(menu)self._menustr=nil if menu then self:set(menu)end end function rtk.NativeMenu:set(menu)self.menu=menu if menu then self:_parse()end end function rtk.NativeMenu:_parse(submenu)self._item_by_idx={}self._item_by_id={}self._order=self:_parse_submenu(self.menu)end function rtk.NativeMenu:_parse_submenu(submenu,baseitem)local order=baseitem or {}for n,menuitem in ipairs(submenu)do if type(menuitem) ~='table' then menuitem={label=menuitem}else menuitem=table.shallow_copy(menuitem)if not menuitem.label then menuitem.label=table.remove(menuitem,1)end end if menuitem.submenu then menuitem=self:_parse_submenu(menuitem.submenu,menuitem)menuitem.submenu=nil elseif menuitem.label~=rtk.NativeMenu.SEPARATOR then local idx=#self._item_by_idx+1 menuitem.index=idx self._item_by_idx[idx]=menuitem end if menuitem.id then self._item_by_id[tostring(menuitem.id)]=menuitem end order[#order+1]=menuitem end return order end local function _get_item_attr(item,attr)local val=item[attr] if type(val)=='function' then return val()else return val end end function rtk.NativeMenu:_build_menustr(submenu,items)items=items or {}local menustr=''for n,item in ipairs(submenu)do if not _get_item_attr(item, 'hidden') then local flags=''if _get_item_attr(item, 'disabled') then flags=flags .. '#'end if _get_item_attr(item, 'checked') then flags=flags .. '!'end if item.label==rtk.NativeMenu.SEPARATOR then menustr=menustr .. '|'elseif #item>0 then menustr=menustr .. flags .. '>' .. item.label .. '|' .. self:_build_menustr(item, items) .. '<|'else items[#items+1]=item menustr=menustr .. flags .. item.label .. '|'end end end return menustr,items end function rtk.NativeMenu:item(idx_or_id)if not idx_or_id or not self._item_by_idx then return nil end local item=self._item_by_id[tostring(idx_or_id)] or self._item_by_id[idx_or_id] if item then return item end return self._item_by_idx[idx_or_id] end function rtk.NativeMenu:items()if not self._item_by_idx then return function()end end local i=0 local n=#self._item_by_idx return function()i=i+1 if i<=n then return self._item_by_idx[i] end end end function rtk.NativeMenu:open(x,y)rtk.window:request_mouse_cursor(rtk.mouse.cursors.POINTER)assert(self.menu, 'menu must be set before open()')if not self._order then self:_parse()end local menustr,items=self:_build_menustr(self._order)local future=rtk.Future()rtk.defer(function()gfx.x=x gfx.y=y local choice=gfx.showmenu(menustr)local item if choice>0 then item=items[tonumber(choice)] end rtk._drag_candidates=nil rtk.window:queue_mouse_refresh()future:resolve(item)end)return future end function rtk.NativeMenu:open_at_mouse()return self:open(gfx.mouse_x,gfx.mouse_y)end function rtk.NativeMenu:open_at_widget(widget,halign,valign)assert(widget.drawn, "rtk.NativeMenu.open_at_widget() called before widget was drawn")local x=widget.clientx local y=widget.clienty if halign=='right' then x=x+widget.calc.w end if valign ~='top' then y=y+widget.calc.h end return self:open(x,y)end end)() __mod_rtk_widget=(function() local rtk=__mod_rtk_core local log=__mod_rtk_log rtk.Widget=rtk.class('rtk.Widget')rtk.Widget.static.LEFT=0 rtk.Widget.static.TOP=0 rtk.Widget.static.CENTER=1 rtk.Widget.static.RIGHT=2 rtk.Widget.static.BOTTOM=2 rtk.Widget.static.POSITION_INFLOW=0x01 rtk.Widget.static.POSITION_FIXED=0x02 rtk.Widget.static.RELATIVE=rtk.Widget.POSITION_INFLOW|0x10 rtk.Widget.static.ABSOLUTE=0x20 rtk.Widget.static.FIXED=rtk.Widget.POSITION_FIXED|0x40 rtk.Widget.static.FIXED_FLOW=rtk.Widget.POSITION_INFLOW|rtk.Widget.POSITION_FIXED|0x80 rtk.Widget.static.BOX=1 rtk.Widget.static.FULL=rtk.Widget.BOX|2 rtk.Widget.static.REFLOW_DEFAULT=nil rtk.Widget.static.REFLOW_NONE=0 rtk.Widget.static.REFLOW_PARTIAL=1 rtk.Widget.static.REFLOW_FULL=2 rtk.Widget.static._calc_border=function(self,value)if type(value)=='string' then local parts=string.split(value)if #parts==1 then return {{rtk.color.rgba(parts[1])},1}elseif #parts==2 then local width=parts[1]:gsub('px', '')return {{rtk.color.rgba(parts[2])},tonumber(width)}else error('invalid border format')end elseif value then assert(type(value)=='table', 'border must be string or table')if #value==1 then return {rtk.color.rgba({value[1]}),1}elseif #value==2 then return value elseif #value==4 then return {value,1}else log.exception('invalid border value: %s', table.tostring(value))error('invalid border value')end end end rtk.Widget.static._calc_padding_or_margin=function(value)if not value then return 0,0,0,0 elseif type(value)=='number' then return value,value,value,value else if type(value)=='string' then local parts=string.split(value)value={}for i=1,#parts do local sz=parts[i]:gsub('px', '')value[#value+1]=tonumber(sz)end end if #value==1 then return value[1],value[1],value[1],value[1] elseif #value==2 then return value[1],value[2],value[1],value[2] elseif #value==3 then return value[1],value[2],value[3],value[2] elseif #value==4 then return value[1],value[2],value[3],value[4] else error('invalid value')end end end rtk.Widget.register{x=rtk.Attribute{default=0,reflow=rtk.Widget.REFLOW_FULL,reflow_uses_exterior_value=true,},y=rtk.Attribute{default=0,reflow=rtk.Widget.REFLOW_FULL,reflow_uses_exterior_value=true,},w=rtk.Attribute{type='number',reflow=rtk.Widget.REFLOW_FULL,reflow_uses_exterior_value=true,animate=function(self,anim,scale)local calculated=anim.resolve(anim.easingfunc(anim.pct))local exterior if anim.doneval and anim.doneval~=rtk.Attribute.NIL and anim.doneval~=rtk.Attribute.DEFAULT then exterior=(anim.pct<1 and calculated or anim.doneval)/(scale or rtk.scale.value)end if anim.dst==0 or anim.dst>1 then exterior = (type(exterior) == 'number' and exterior > 0 and exterior <= 1.0) and 1.01 or exterior end return calculated,exterior end,},h=rtk.Attribute{type='number',reflow=rtk.Widget.REFLOW_FULL,reflow_uses_exterior_value=true,animate=rtk.Reference('w'),},z=rtk.Attribute{default=0,reflow=rtk.Widget.REFLOW_FULL},minw = rtk.Attribute{type='number', reflow=rtk.Widget.REFLOW_FULL, reflow_uses_exterior_value=true},minh = rtk.Attribute{type='number', reflow=rtk.Widget.REFLOW_FULL, reflow_uses_exterior_value=true},maxw = rtk.Attribute{type='number', reflow=rtk.Widget.REFLOW_FULL, reflow_uses_exterior_value=true},maxh = rtk.Attribute{type='number', reflow=rtk.Widget.REFLOW_FULL, reflow_uses_exterior_value=true},halign=rtk.Attribute{default=rtk.Widget.LEFT,calculate={left=rtk.Widget.LEFT,center=rtk.Widget.CENTER,right=rtk.Widget.RIGHT},},valign=rtk.Attribute{default=rtk.Widget.TOP,calculate={top=rtk.Widget.TOP,center=rtk.Widget.CENTER,bottom=rtk.Widget.BOTTOM},},scalability=rtk.Attribute{default=rtk.Widget.FULL,reflow=rtk.Widget.REFLOW_FULL,calculate={box=rtk.Widget.BOX,full=rtk.Widget.FULL},},position=rtk.Attribute{default=rtk.Widget.RELATIVE,reflow=rtk.Widget.REFLOW_FULL,calculate={relative=rtk.Widget.RELATIVE,absolute=rtk.Widget.ABSOLUTE,fixed=rtk.Widget.FIXED,['fixed-flow']=rtk.Widget.FIXED_FLOW },},box=nil,offx=nil,offy=nil,clientx=nil,clienty=nil,padding=rtk.Attribute{replaces={'tpadding', 'rpadding', 'bpadding', 'lpadding'},get=function(self,attr,target)return {target.tpadding,target.rpadding,target.bpadding,target.lpadding}end,reflow=rtk.Widget.REFLOW_FULL,calculate=function(self,attr,value,target)local t,r,b,l=rtk.Widget.static._calc_padding_or_margin(value)target.tpadding,target.rpadding,target.bpadding,target.lpadding=t,r,b,l return {t,r,b,l}end },tpadding=rtk.Attribute{priority=true,reflow=rtk.Widget.REFLOW_FULL},rpadding=rtk.Reference('tpadding'),bpadding=rtk.Reference('tpadding'),lpadding=rtk.Reference('tpadding'),margin=rtk.Attribute{default=0,replaces={'tmargin', 'rmargin', 'bmargin', 'lmargin'},get=function(self,attr,target)return {target.tmargin,target.rmargin,target.bmargin,target.lmargin}end,reflow=rtk.Widget.REFLOW_FULL,calculate=function(self,attr,value,target)local t,r,b,l=rtk.Widget.static._calc_padding_or_margin(value)target.tmargin,target.rmargin,target.bmargin,target.lmargin=t,r,b,l return {t,r,b,l}end },tmargin=rtk.Attribute{priority=true,reflow=rtk.Widget.REFLOW_FULL},rmargin=rtk.Reference('tmargin'),bmargin=rtk.Reference('tmargin'),lmargin=rtk.Reference('tmargin'),border=rtk.Attribute{reflow=rtk.Widget.REFLOW_FULL,calculate=function(self,attr,value,target)local border=rtk.Widget.static._calc_border(self,value)target.tborder=border target.rborder=border target.bborder=border target.lborder=border target.border_uniform=true return border end },tborder=rtk.Attribute{priority=true,reflow=rtk.Widget.REFLOW_FULL,calculate=function(self,attr,value,target)target.border_uniform=false return rtk.Widget.static._calc_border(self,value)end,},rborder=rtk.Reference('tborder'),bborder=rtk.Reference('tborder'),lborder=rtk.Reference('tborder'),visible=rtk.Attribute{default=true,reflow=rtk.Widget.REFLOW_FULL},disabled=false,ghost=rtk.Attribute{default=false,reflow=rtk.Widget.REFLOW_NONE,},tooltip=nil,cursor=nil,alpha=rtk.Attribute{default=1.0,reflow=rtk.Widget.REFLOW_NONE,},autofocus=nil,bg=rtk.Attribute{reflow=rtk.Widget.REFLOW_NONE,calculate=function(self,attr,value,target,animation)if not value and animation then local parent=self.parent value=parent and parent.calc.bg or rtk.theme.bg end return value and {rtk.color.rgba(value)}end,},hotzone=rtk.Attribute{reflow=rtk.Widget.REFLOW_NONE,replaces={'thotzone', 'rhotzone', 'bhotzone', 'lhotzone'},get=function(self,attr,target)return {target.thotzone,target.rhotzone,target.bhotzone,target.lhotzone}end,calculate=function(self,attr,value,target)local t,r,b,l=rtk.Widget.static._calc_padding_or_margin(value)target.thotzone,target.rhotzone,target.bhotzone,target.lhotzone=t,r,b,l target._hotzone_set=true return {t,r,b,l}end },thotzone=rtk.Attribute{priority=true,reflow=rtk.Widget.REFLOW_NONE,calculate=function(self,attr,value,target)target._hotzone_set=true return value end,},rhotzone=rtk.Reference('thotzone'),bhotzone=rtk.Reference('thotzone'),lhotzone=rtk.Reference('thotzone'),scroll_on_drag=true,show_scrollbar_on_drag=true,touch_activate_delay=nil,realized=false,drawn=false,viewport=nil,window=nil,mouseover=false,hovering=false,debug=nil,id=nil,ref=nil,refs=nil,}local _refs_metatable={__mode='v',__index=function(table,key)return table.__self:_ref(table,key)end,__newindex=function(table,key,value)rawset(table,key,value)table.__empty=false end }local _calc_metatable={__call=function(table,_,attr,instant)return table.__self:_calc(attr,instant)end }rtk.Widget.static.last_index=0 function rtk.Widget:__allocate()self.__id=tostring(rtk.Widget.static.last_index)rtk.Widget.static.last_index=rtk.Widget.static.last_index+1 end function rtk.Widget:initialize(attrs,...)self.refs=setmetatable({__empty=true,__self=self},_refs_metatable)self.calc=setmetatable({__self=self,border_uniform=true},_calc_metatable)local clsattrs=self.class.attributes local tables={clsattrs.defaults,...}local merged={}for n=1,#tables do for k,v in pairs(tables[n])do merged[k]=v end end if attrs then for k,v in pairs(attrs)do local meta=clsattrs[k] or rtk.Attribute.NIL local attr=meta.alias if attr then merged[attr]=v end local replaces=meta.replaces if replaces then for n=1,#replaces do merged[replaces[n]]=nil end end if not tonumber(k)then merged[k]=v end end if attrs.ref then rtk._refs[attrs.ref]=self self.refs[attrs.ref]=self end end self.id=self.__id self:_setattrs(merged)self._last_mousedown_time=0 self._last_reflow_scale=nil end function rtk.Widget:__tostring()local clsname=self.class.name:gsub('rtk.', '')if not self.calc then return string.format('<%s (uninitialized)>', clsname)end local info=self:__tostring_info()info=info and string.format('<%s>', info) or ''return string.format('%s%s[%s] (%s,%s %sx%s)',clsname,info,self.id,self.calc.x,self.calc.y,self.calc.w,self.calc.h )end function rtk.Widget:__tostring_info()end function rtk.Widget:_setattrs(attrs)if not attrs then return end local clsattrs=self.class.attributes local priority={}local calc=self.calc for k,v in pairs(attrs)do local meta=clsattrs[k] if meta and not meta.priority then if v==rtk.Attribute.FUNCTION then v=clsattrs[k].default_func(self,k)elseif v==rtk.Attribute.NIL then v=nil end local calculated=self:_calc_attr(k,v,nil,meta)self:_set_calc_attr(k,v,calculated,calc,meta)else priority[#priority+1]=k end self[k]=v end if #priority==0 then return end for _,k in ipairs(priority)do local v=self[k] if v==rtk.Attribute.FUNCTION then v=clsattrs[k].default_func(self,k)self[k]=v end if v~=nil then if v==rtk.Attribute.NIL then v=nil self[k]=nil end local calculated=self:_calc_attr(k,v)self:_set_calc_attr(k,v,calculated,calc)end end end function rtk.Widget:_ref(table,key)if self.parent then return self.parent.refs[key] else return rtk._refs[key] end end function rtk.Widget:_get_debug_color()if not self.debug_color then local x=self.id:hash()*100 x=x ~(x<<13)x=x ~(x>>7)x=x ~(x<<17)local color=table.pack(rtk.color.rgba(x%16777216))local luma=rtk.color.luma(color)if luma<0.2 then color=table.pack(rtk.color.mod(color,1,1,2.5))elseif luma>0.8 then color=table.pack(rtk.color.mod(color,1,1,0.75))end self.debug_color=color end return self.debug_color end function rtk.Widget:_draw_debug_box(offx,offy,event)local calc=self.calc if not self.debug and not rtk.debug or not calc.w then return false end if not self.debug and event.debug~=self then return false end local color=self:_get_debug_color()gfx.set(color[1],color[2],color[3],0.2)local x=calc.x+offx local y=calc.y+offy gfx.rect(x,y,calc.w,calc.h,1)gfx.set(color[1],color[2],color[3],0.4)gfx.rect(x,y,calc.w,calc.h,0)local tp,rp,bp,lp=self:_get_padding_and_border()if tp>0 or rp>0 or bp>0 or lp>0 then gfx.set(color[1],color[2],color[3],0.8)gfx.rect(x+lp,y+tp,calc.w-lp-rp,calc.h-tp-bp,0)end return true end function rtk.Widget:_draw_debug_info(event)local calc=self.calc local parts={{ 15, "#6e2e2e", tostring(self.class.name:gsub("rtk.", "")) },{ 15, "#378b48", string.format('#%s', self.id) },{ 17, "#cccccc", " | " },{ 15, "#555555", string.format("%.1f", calc.x) },{ 15, "#777777", " , " },{ 15, "#555555", string.format("%.1f", calc.y) },{ 17, "#cccccc", " | " },{ 15, "#555555", string.format("%.1f", calc.w) },{ 13, "#777777", " x " },{ 15, "#555555", string.format("%.1f", calc.h) },}local sizes={}local bw,bh=0,0 for n,part in ipairs(parts)do local sz,_,str=table.unpack(part)gfx.setfont(1,rtk.theme.default_font,sz)local w,h=gfx.measurestr(str)sizes[n]={w,h}bw=bw+w bh=math.max(bh,h)end bw=bw+20 bh=bh+10 local x=self.clientx local y=self.clienty if x+bw>self.window.calc.w then x=self.window.calc.w-bw elseif x<0 then x=0 end if y-bh>=0 then y=math.max(0,y-bh)else y=math.min(y+calc.h,self.window.calc.h-bh)end rtk.color.set('#ffffff')gfx.rect(x,y,bw,bh,1)rtk.color.set('#777777')gfx.rect(x,y,bw,bh,0)gfx.x=x+10 for n,part in ipairs(parts)do local sz,color,str=table.unpack(part)rtk.color.set(color)gfx.y=y+(bh-sizes[n][2])/2 gfx.setfont(1,rtk.theme.default_font,sz)gfx.drawstr(str)end end function rtk.Widget:attr(attr,value,trigger,reflow)return self:_attr(attr,value,trigger,reflow,nil,false)end function rtk.Widget:sync(attr,value,calculated,trigger,reflow)return self:_attr(attr,value,trigger,reflow,calculated,true)end function rtk.Widget:_attr(attr,value,trigger,reflow,calculated,sync)local meta=self.class.attributes.get(attr)if value==rtk.Attribute.DEFAULT then if meta.default==rtk.Attribute.FUNCTION then value=meta.default_func(self,attr)else value=meta.default end elseif value==rtk.Attribute.NIL then value=nil end local oldval=self[attr] local oldcalc=self.calc[attr] local replaces=meta.replaces if replaces then for i=1,#replaces do self[replaces[i]]=nil end end if calculated==nil then calculated=self:_calc_attr(attr,value,nil,meta)end if not rawequal(value,oldval)or calculated~=oldcalc or replaces or trigger then self[attr]=value self:_set_calc_attr(attr,value,calculated,self.calc,meta)self:_handle_attr(attr,calculated,oldcalc,trigger==nil or trigger,reflow,sync)end return self end function rtk.Widget:_calc_attr(attr,value,target,meta,namespace,widget)target=target or self.calc meta=meta or self.class.attributes.get(attr)if meta.type then value=meta.type(value)end local calculate=meta.calculate if calculate then local tp=type(calculate)if tp=='table' then if value==nil then value=calculate[rtk.Attribute.NIL] else value=calculate[value] or value end elseif tp=='function' then if value==rtk.Attribute.NIL then value=nil end value=calculate(self,attr,value,target)end end return value end function rtk.Widget:_set_calc_attr(attr,value,calculated,target,meta)meta=meta or self.class.attributes.get(attr)if meta.set then meta.set(self,attr,value,calculated,target)else self.calc[attr]=calculated end end function rtk.Widget:_calc(attr,instant)if not instant then local anim=self:get_animation(attr)if anim and anim.dst then return anim.dst end end local meta=self.class.attributes.get(attr)if meta.get then return meta.get(self,attr,self.calc)else return self.calc[attr] end end function rtk.Widget:move(x,y)self:attr('x', x)self:attr('y', y)return self end function rtk.Widget:resize(w,h)self:attr('w', w)self:attr('h', h)return self end function rtk.Widget:_get_relative_pos_to_viewport()local x,y=0,0 local widget=self while widget do x=x+widget.calc.x y=y+widget.calc.y if widget.viewport and widget.viewport==widget.parent then break end widget=widget.parent end return x,y end function rtk.Widget:scrolltoview(margin,allowh,allowv,smooth)if not self.visible or not self.box or not self.viewport then return self end local calc=self.calc local vcalc=self.viewport.calc local tmargin,rmargin,bmargin,lmargin=rtk.Widget.static._calc_padding_or_margin(margin or 0)local left,top=nil,nil local absx,absy=self:_get_relative_pos_to_viewport()if allowh~=false then if absx-lmarginself.viewport.scroll_left+vcalc.w then left=absx+calc.w+rmargin-vcalc.w end end if allowv~=false then if absy-tmarginself.viewport.scroll_top+vcalc.h then top=absy+calc.h+bmargin-vcalc.h end end self.viewport:scrollto(left,top,smooth)return self end function rtk.Widget:hide()if self.calc.visible~=false then return self:attr('visible', false)end return self end function rtk.Widget:show()if self.calc.visible~=true then return self:attr('visible', true)end return self end function rtk.Widget:toggle()if self.calc.visible==true then return self:hide()else return self:show()end end function rtk.Widget:focused(event)return rtk.focused==self end function rtk.Widget:focus(event)if rtk.focused and rtk.focused~=self then rtk.focused:blur(event,self)end if rtk.focused==nil and self:_handle_focus(event)~=false then rtk.focused=self if self.parent then self.parent:_set_focused_child(self)end self:queue_draw()return true end return false end function rtk.Widget:blur(event,other)if not self:focused(event)then return true end if self:_handle_blur(event,other)~=false then rtk.focused=nil if self.parent then self.parent:_set_focused_child(nil)end self:queue_draw()return true end return false end function rtk.Widget:animate(kwargs)assert(kwargs and (kwargs.attr or #kwargs > 0), 'missing animation arguments')local calc=self.calc local attr=kwargs.attr or kwargs[1] local meta=self.class.attributes.get(attr)local key=string.format('%s.%s', self.id, attr)local curanim=rtk._animations[key] local curdst=curanim and curanim.dst or self.calc[attr] if curdst == kwargs.dst and not meta.calculate and attr ~= 'w' and attr ~= 'h' then if curanim then return curanim.future elseif not kwargs.src then return rtk.Future():resolve(self)end end kwargs.attr=attr kwargs.key=key kwargs.widget=self kwargs.attrmeta=meta kwargs.stepfunc=(meta.animate and meta.animate~=rtk.Attribute.NIL)and meta.animate kwargs.calculate=meta.calculate kwargs.sync_exterior_value=meta.reflow_uses_exterior_value if kwargs.dst==rtk.Attribute.DEFAULT then if meta.default==rtk.Attribute.FUNCTION then kwargs.dst=meta.default_func(self,attr)else kwargs.dst=meta.default end end local calcsrc,calcdst local doneval=kwargs.dst or rtk.Attribute.DEFAULT if attr == 'w' or attr == 'h' then if(not kwargs.src or kwargs.src==rtk.Attribute.NIL)or(kwargs.src<=1.0 and kwargs.src>=0)then if kwargs.src==rtk.Attribute.NIL then kwargs.src=nil end kwargs.src=(calc[attr] or 0)*(kwargs.src or 1)calcsrc=true end if(not kwargs.dst or kwargs.dst==rtk.Attribute.NIL)or(kwargs.dst<=1.0 and kwargs.dst>0)then if kwargs.dst==rtk.Attribute.NIL then kwargs.dst=nil end local current=self[attr] local current_calc=calc[attr] self[attr]=kwargs.dst calc[attr]=meta.calculate and meta.calculate(self,attr,kwargs.dst,{},true)or kwargs.dst local window=self:_slow_get_window()if not window then return rtk.Future():resolve(self)end window:reflow(rtk.Widget.REFLOW_FULL)kwargs.dst=calc[attr] or 0 calcdst=true self[attr]=current calc[attr]=current_calc window:reflow(rtk.Widget.REFLOW_FULL)end end if not calcdst and meta.calculate then kwargs.dst=meta.calculate(self,attr,kwargs.dst,{},true)doneval=kwargs.dst or rtk.Attribute.DEFAULT end if curdst==kwargs.dst then if curanim then return curanim.future elseif not kwargs.src then return rtk.Future():resolve(self)end end if kwargs.doneval==nil then kwargs.doneval=doneval end if not kwargs.src then kwargs.src=self:calc(attr,true)calc[attr]=kwargs.src calcsrc=kwargs.src~=nil end if not calcsrc and meta.calculate then kwargs.src=meta.calculate(self,attr,kwargs.src,{},true)calc[attr]=kwargs.src end return rtk.queue_animation(kwargs)end function rtk.Widget:cancel_animation(attr)local anim=self:get_animation(attr)if anim then anim.future:cancel()end return anim end function rtk.Widget:get_animation(attr)local key=self.id .. '.' .. attr return rtk._animations[key] end function rtk.Widget:setcolor(color,amul)rtk.color.set(color,(amul or 1)*self.calc.alpha)return self end function rtk.Widget:queue_draw()if self.window then self.window:queue_draw()end return self end function rtk.Widget:queue_reflow(mode,widget)local window=self:_slow_get_window()if window then window:queue_reflow(mode,widget or self)end return self end function rtk.Widget:reflow(boxx,boxy,boxw,boxh,fillw,fillh,clampw,clamph,uiscale,viewport,window,greedyw,greedyh)local calc=self.calc local expw,exph if not boxx then if self.box then expw,exph=self:_reflow(table.unpack(self.box))else return end else self.viewport=viewport self.window=window self.box={boxx,boxy,boxw,boxh,fillw,fillh,clampw,clamph,uiscale,viewport,window,greedyw,greedyh}expw,exph=self:_reflow(boxx,boxy,boxw,boxh,fillw,fillh,clampw,clamph,uiscale,viewport,window,greedyw,greedyh)end self.realized=true self:onreflow()return calc.x,calc.y,calc.w,calc.h,expw or fillw,exph or fillh end function rtk.Widget:_get_padding()local calc=self.calc local scale=rtk.scale.value return (calc.tpadding or 0)*scale,(calc.rpadding or 0)*scale,(calc.bpadding or 0)*scale,(calc.lpadding or 0)*scale end function rtk.Widget:_get_border_sizes()local calc=self.calc return calc.tborder and calc.tborder[2] or 0,calc.rborder and calc.rborder[2] or 0,calc.bborder and calc.bborder[2] or 0,calc.lborder and calc.lborder[2] or 0 end function rtk.Widget:_get_padding_and_border()local tp,rp,bp,lp=self:_get_padding()local tb,rb,bb,lb=self:_get_border_sizes()return tp+tb,rp+rb,bp+bb,lp+lb end function rtk.Widget:_adjscale(val,scale,box)if not val then return elseif val>0 and val<=1.0 and box then return val*box elseif(self.calc.scalability&rtk.Widget.FULL~=rtk.Widget.FULL)then return val else return val*(scale or rtk.scale.value)end end function rtk.Widget:_get_box_pos(boxx,boxy)local x=self.x or 0 local y=self.y or 0 if self.calc.scalability&rtk.Widget.FULL==rtk.Widget.FULL then local scale=rtk.scale.value return scale*x+boxx,scale*y+boxy else return x+boxx,y+boxy end end local function _get_content_dimension(size,box,padding,fill,clamp,greedy,scale)if size then if box and size<-1 then return box+(size*scale)-padding elseif box and size<=1.0 then return greedy and math.abs(box*size)-padding else return(size*scale)-padding end end if fill and box and greedy then return box-padding end end function rtk.Widget:_get_content_size(boxw,boxh,fillw,fillh,clampw,clamph,scale,greedyw,greedyh)scale=self:_adjscale(scale or 1)local tp,rp,bp,lp=self:_get_padding_and_border()local w=_get_content_dimension(self.w,boxw,lp+rp,fillw,clampw,greedyw,scale)local h=_get_content_dimension(self.h,boxh,tp+bp,fillh,clamph,greedyh,scale)local minw,maxw,minh,maxh=self:_get_min_max_sizes(boxw,boxh,greedyw,greedyh,scale)maxw=maxw and clampw and math.min(maxw,boxw)or maxw maxh=maxh and clamph and math.min(maxh,boxh)or maxh minw=minw and minw-lp-rp maxw=maxw and maxw-lp-rp minh=minh and minh-tp-bp maxh=maxh and maxh-tp-bp return w,h,tp,rp,bp,lp,minw,maxw,minh,maxh end function rtk.Widget:_get_min_max_sizes(boxw,boxh,greedyw,greedyh,scale)local minw,maxw,minh,maxh=self.minw,self.maxw,self.minh,self.maxh return minw and((minw>1 or minw<=0)and(minw*scale)or(greedyw and minw*boxw)),maxw and((maxw>1 or maxw<=0)and(maxw*scale)or(greedyw and maxw*boxw)),minh and((minh>1 or minh<=0)and(minh*scale)or(greedyh and minh*boxh)),maxh and((maxh>1 or maxh<=0)and(maxh*scale)or(greedyh and maxh*boxh))end function rtk.Widget:_reflow(boxx,boxy,boxw,boxh,fillw,fillh,clampw,clamph,uiscale,viewport,window,greedyw,greedyh)local calc=self.calc calc.x,calc.y=self:_get_box_pos(boxx,boxy)local w,h,tp,rp,bp,lp,minw,maxw,minh,maxh=self:_get_content_size(boxw,boxh,fillw,fillh,clampw,clamph,nil,greedyw,greedyh )calc.w=rtk.clamp(w or(fillw and greedyw and(boxw-lp-rp)or 0),minw,maxw)+lp+rp calc.h=rtk.clamp(h or(fillh and greedyh and(boxh-tp-bp)or 0),minh,maxh)+tp+bp return fillw and greedyw,fillh and greedyh end function rtk.Widget:_realize_geometry()self.realized=true end function rtk.Widget:_slow_get_window()if self.window then return self.window end local w=self.parent while w do if w.window then return w.window end w=w.parent end end function rtk.Widget:_is_mouse_over(clparentx,clparenty,event)local calc=self.calc local x,y=calc.x+clparentx,calc.y+clparenty local w,h=calc.w,calc.h if calc._hotzone_set then local scale=rtk.scale.value local l=(calc.lhotzone or 0)*scale local t=(calc.thotzone or 0)*scale x=x-l y=y-t w=w+l+(calc.rhotzone or 0)*scale h=h+t+(calc.bhotzone or 0)*scale end return self.window and self.window.in_window and rtk.point_in_box(event.x,event.y,x,y,w,h)end function rtk.Widget:_draw(offx,offy,alpha,event,clipw,cliph,cltargetx,cltargety,parentx,parenty)self.offx=offx self.offy=offy self.clientx=cltargetx+offx+self.calc.x self.clienty=cltargety+offy+self.calc.y self.drawn=true end function rtk.Widget:_draw_bg(offx,offy,alpha,event)local calc=self.calc if calc.bg and not calc.ghost then self:setcolor(calc.bg,alpha)gfx.rect(calc.x+offx,calc.y+offy,calc.w,calc.h,1)end end function rtk.Widget:_draw_tooltip(clientx,clienty,clientw,clienth,tooltip)tooltip=tooltip or self.calc.tooltip local font=rtk.Font(table.unpack(rtk.theme.tooltip_font))local segments,w,h=font:layout(tooltip,clientw-10,clienth-10,true)rtk.color.set(rtk.theme.tooltip_bg)local x=rtk.clamp(clientx,0,clientw-w-10)local y=rtk.clamp(clienty+16,0,clienth-h-10-self.calc.h)gfx.rect(x,y,w+10,h+10,1)rtk.color.set(rtk.theme.tooltip_text)gfx.rect(x,y,w+10,h+10,0)font:draw(segments,x+5,y+5,w,h)end function rtk.Widget:_unpack_border(border,alpha)local color,thickness=table.unpack(border)if color then self:setcolor(color or rtk.theme.button,alpha*self.calc.alpha)end return thickness or 1 end function rtk.Widget:_draw_borders(offx,offy,alpha,all)if self.ghost then return end local calc=self.calc if not all and calc.border_uniform and not calc.tborder then return end local x,y,w,h=calc.x+offx,calc.y+offy,calc.w,calc.h local tb,rb,bb,lb all=all or(calc.border_uniform and calc.tborder)if all then local thickness=self:_unpack_border(all,alpha)if thickness==1 then gfx.rect(x,y,w,h,0)return elseif thickness==0 then return else tb,rb,bb,lb=all,all,all,all end else tb,rb,bb,lb=calc.tborder,calc.rborder,calc.bborder,calc.lborder end if tb then local thickness=self:_unpack_border(tb,alpha)gfx.rect(x,y,w,thickness,1)end if rb and w>0 then local thickness=self:_unpack_border(rb,alpha)gfx.rect(x+w-thickness,y,thickness,h,1)end if bb and h>0 then local thickness=self:_unpack_border(bb,alpha)gfx.rect(x,y+h-thickness,w,thickness,1)end if lb then local thickness=self:_unpack_border(lb,alpha)gfx.rect(x,y,thickness,h,1)end end function rtk.Widget:_get_touch_activate_delay(event)if not rtk.touchscroll then return self.touch_activate_delay or 0 else if not self.viewport or not self.viewport:scrollable()then return 0 end return(not self:focused(event)and event.button==rtk.mouse.BUTTON_LEFT)and self.touch_activate_delay or rtk.touch_activate_delay end end function rtk.Widget:_should_handle_event(listen)if not listen and rtk._modal and rtk._modal[self.id]~=nil then return true else return listen end end function rtk.Widget:_handle_event(clparentx,clparenty,event,clipped,listen)local calc=self.calc if not listen and rtk._modal and rtk._modal[self.id]==nil then return false end local dnd=rtk.dnd if not clipped and self:_is_mouse_over(clparentx,clparenty,event)then event:set_widget_mouseover(self,clparentx,clparenty)if event.type==rtk.Event.MOUSEMOVE and not calc.disabled then if dnd.dragging==self then if calc.cursor then self.window:request_mouse_cursor(calc.cursor)end self:_handle_dragmousemove(event,dnd.arg)elseif self.hovering==false then if event.buttons==0 or self:focused(event)then if not event.handled and not self.mouseover and self:_handle_mouseenter(event)then self.hovering=true self:_handle_mousemove(event)self:queue_draw()elseif event.handled and self.mouseover then self.mouseover=false elseif rtk.debug then self:queue_draw()end else if dnd.arg and not event.simulated and rtk.dnd.droppable then if dnd.dropping==self or self:_handle_dropfocus(event,dnd.dragging,dnd.arg)then if dnd.dropping then if dnd.dropping~=self then dnd.dropping:_handle_dropblur(event,dnd.dragging,dnd.arg)elseif not event.simulated then dnd.dropping:_handle_dropmousemove(event,dnd.dragging,dnd.arg)end end event:set_handled(self)self:queue_draw()dnd.dropping=self end end end if not self.mouseover and(not event.handled or event.handled==self)and event.buttons==0 then self.mouseover=true self:queue_draw()end else if event.handled then self:_handle_mouseleave(event)self.hovering=false self.mouseover=false self:queue_draw()else self.mouseover=true self:_handle_mousemove(event)event:set_handled(self)end end elseif event.type==rtk.Event.MOUSEDOWN and not calc.disabled then local duration=event:get_button_duration()if duration==0 then event:set_widget_pressed(self)end if not event.handled then local state=event:get_button_state(self)or 0 local threshold=self:_get_touch_activate_delay(event)if duration>=threshold and state==0 and event:is_widget_pressed(self)then event:set_button_state(self,1)if self:_handle_mousedown(event)~=false then self:_accept_mousedown(event,duration,state)end elseif state&8==0 then if duration>=rtk.long_press_delay then if self:_handle_longpress(event)then self:queue_draw()event:set_button_state(self,state|8|16)else event:set_button_state(self,state|8)end end end if self:focused(event)then event:set_handled(self)end end elseif event.type==rtk.Event.MOUSEUP and not calc.disabled then if not event.handled then if not dnd.dragging then self:_deferred_mousedown(event)end if self:_handle_mouseup(event)then event:set_handled(self)self:queue_draw()end local state=event:get_button_state(self)or 0 if state&2~=0 then if state&16==0 and not dnd.dragging then if self:_handle_click(event)then event:set_handled(self)self:queue_draw()end local last=rtk.mouse.last[event.button] if state&4~=0 then if self:_handle_doubleclick(event)then event:set_handled(self)self:queue_draw()end self._last_mousedown_time=0 end end end end if dnd.dropping==self then self:_handle_dropblur(event,dnd.dragging,dnd.arg)if self:_handle_drop(event,dnd.dragging,dnd.arg)then event:set_handled(self)self:queue_draw()end end self:queue_draw()elseif event.type==rtk.Event.MOUSEWHEEL and not calc.disabled then if not event.handled and self:_handle_mousewheel(event)then event:set_handled(self)self:queue_draw()end elseif event.type==rtk.Event.DROPFILE and not calc.disabled then if not event.handled and self:_handle_dropfile(event)then event:set_handled(self)self:queue_draw()end end elseif event.type==rtk.Event.MOUSEMOVE then self.mouseover=false if dnd.dragging==self then self.window:request_mouse_cursor(calc.cursor)self:_handle_dragmousemove(event,dnd.arg)end if self.hovering==true then if dnd.dragging~=self then self:_handle_mouseleave(event)self:queue_draw()self.hovering=false end elseif event.buttons~=0 and dnd.dropping then if dnd.dropping==self then self:_handle_dropblur(event,dnd.dragging,dnd.arg)dnd.dropping=nil end self:queue_draw()end else self.mouseover=false end if rtk.touchscroll and event.type==rtk.Event.MOUSEUP and self:focused(event)then if event:get_button_state('mousedown-handled') == self then event:set_handled(self)self:queue_draw()end end if event.type==rtk.Event.KEY and not event.handled then if self:focused(event)and self:_handle_keypress(event)then event:set_handled(self)self:queue_draw()end end if event.type==rtk.Event.WINDOWCLOSE then self:_handle_windowclose(event)end if(self.mouseover or dnd.dragging==self)and calc.cursor then self.window:request_mouse_cursor(calc.cursor)end return true end function rtk.Widget:_deferred_mousedown(event,x,y)local mousedown_handled=event:get_button_state('mousedown-handled')if not mousedown_handled and event:is_widget_pressed(self)and not event:get_button_state(self)then local downevent=event:clone{type=rtk.Event.MOUSEDOWN,simulated=true,x=x or event.x,y=y or event.y}if self:_handle_mousedown(downevent)then self:_accept_mousedown(event)end end end function rtk.Widget:_accept_mousedown(event,duration,state)event:set_button_state('mousedown-handled', self)event:set_handled(self)if not event.simulated and event.time-self._last_mousedown_time<=rtk.double_click_delay then event:set_button_state(self,(state or 0)|2|4)self._last_mousedown_time=0 else event:set_button_state(self,(state or 0)|2)self._last_mousedown_time=event.time end self:queue_draw()end function rtk.Widget:_unrealize()self.realized=false end function rtk.Widget:_release_modal(event)end function rtk.Widget:onattr(attr,value,oldval,trigger,sync)return true end function rtk.Widget:_handle_attr(attr,value,oldval,trigger,reflow,sync)local ok=self:onattr(attr,value,oldval,trigger,sync)if ok~=false then local redraw if reflow==rtk.Widget.REFLOW_DEFAULT then local meta=self.class.attributes.get(attr)reflow=meta.reflow or rtk.Widget.REFLOW_PARTIAL redraw=meta.redraw end if reflow~=rtk.Widget.REFLOW_NONE then self:queue_reflow(reflow)elseif redraw~=false then self:queue_draw()end if attr=='visible' then if not value then self:_unrealize()end self.realized=false self.drawn=false elseif attr=='ref' then assert(not oldval, 'ref cannot be changed')self.refs[self.ref]=self rtk._refs[self.ref]=self if self.parent then self.parent:_sync_child_refs(self, 'add')end end end return ok end function rtk.Widget:ondrawpre(offx,offy,alpha,event)end function rtk.Widget:_handle_drawpre(offx,offy,alpha,event)return self:ondrawpre(offx,offy,alpha,event)end function rtk.Widget:ondraw(offx,offy,alpha,event)end function rtk.Widget:_handle_draw(offx,offy,alpha,event)return self:ondraw(offx,offy,alpha,event)end function rtk.Widget:onmousedown(event)end function rtk.Widget:_handle_mousedown(event)local ok=self:onmousedown(event)if ok~=false then local autofocus=self.calc.autofocus if autofocus or (autofocus==nil and self.onclick~=rtk.Widget.onclick)then self:focus(event)return ok or self:focused(event)else return ok or false end end return ok end function rtk.Widget:onmouseup(event)end function rtk.Widget:_handle_mouseup(event)return self:onmouseup(event)end function rtk.Widget:onmousewheel(event)end function rtk.Widget:_handle_mousewheel(event)return self:onmousewheel(event)end function rtk.Widget:onclick(event)end function rtk.Widget:_handle_click(event)return self:onclick(event)end function rtk.Widget:ondoubleclick(event)end function rtk.Widget:_handle_doubleclick(event)return self:ondoubleclick(event)end function rtk.Widget:onlongpress(event)end function rtk.Widget:_handle_longpress(event)return self:onlongpress(event)end function rtk.Widget:onmouseenter(event)end function rtk.Widget:_handle_mouseenter(event)local ok=self:onmouseenter(event)if ok~=false then return self.calc.autofocus or ok end return ok end function rtk.Widget:onmouseleave(event)end function rtk.Widget:_handle_mouseleave(event)return self:onmouseleave(event)end function rtk.Widget:onmousemove(event)end rtk.Widget.onmousemove=nil function rtk.Widget:_handle_mousemove(event)if self.onmousemove then return self:onmousemove(event)end end function rtk.Widget:onkeypress(event)end function rtk.Widget:_handle_keypress(event)return self:onkeypress(event)end function rtk.Widget:onfocus(event)return true end function rtk.Widget:_handle_focus(event)return self:onfocus(event)end function rtk.Widget:onblur(event,other)return true end function rtk.Widget:_handle_blur(event,other)return self:onblur(event,other)end function rtk.Widget:ondragstart(event,x,y,t)end function rtk.Widget:_handle_dragstart(event,x,y,t)local draggable,droppable=self:ondragstart(event,x,y,t)if draggable==nil then return false,false end return draggable,droppable end function rtk.Widget:ondragend(event,dragarg)end function rtk.Widget:_handle_dragend(event,dragarg)self._last_mousedown_time=0 return self:ondragend(event,dragarg)end function rtk.Widget:ondragmousemove(event,dragarg)end function rtk.Widget:_handle_dragmousemove(event,dragarg)return self:ondragmousemove(event,dragarg)end function rtk.Widget:ondropfocus(event,source,dragarg)return false end function rtk.Widget:_handle_dropfocus(event,source,dragarg)return self:ondropfocus(event,source,dragarg)end function rtk.Widget:ondropmousemove(event,source,dragarg)end function rtk.Widget:_handle_dropmousemove(event,source,dragarg)return self:ondropmousemove(event,source,dragarg)end function rtk.Widget:ondropblur(event,source,dragarg)end function rtk.Widget:_handle_dropblur(event,source,dragarg)return self:ondropblur(event,source,dragarg)end function rtk.Widget:ondrop(event,source,dragarg)return false end function rtk.Widget:_handle_drop(event,source,dragarg)return self:ondrop(event,source,dragarg)end function rtk.Widget:onreflow()end function rtk.Widget:_handle_reflow()return self:onreflow()end function rtk.Widget:ondropfile(event)end function rtk.Widget:_handle_dropfile(event)return self:ondropfile(event)end function rtk.Widget:_handle_windowclose(event)end end)() __mod_rtk_viewport=(function() local rtk=__mod_rtk_core rtk.Viewport=rtk.class('rtk.Viewport', rtk.Widget)rtk.Viewport.static.SCROLLBAR_NEVER=0 rtk.Viewport.static.SCROLLBAR_HOVER=1 rtk.Viewport.static.SCROLLBAR_AUTO=2 rtk.Viewport.static.SCROLLBAR_ALWAYS=3 rtk.Viewport.register{[1]=rtk.Attribute{alias='child'},child=rtk.Attribute{reflow=rtk.Widget.REFLOW_FULL},scroll_left=rtk.Attribute{default=0,reflow=rtk.Widget.REFLOW_NONE,calculate=function(self,attr,value,target)return math.round(value)end,},scroll_top=rtk.Reference('scroll_left'),smoothscroll=rtk.Attribute{reflow=rtk.Widget.REFLOW_NONE},scrollbar_size=15,vscrollbar=rtk.Attribute{default=rtk.Viewport.SCROLLBAR_HOVER,calculate={never=rtk.Viewport.SCROLLBAR_NEVER,always=rtk.Viewport.SCROLLBAR_ALWAYS,hover=rtk.Viewport.SCROLLBAR_HOVER,auto=rtk.Viewport.SCROLLBAR_AUTO,},},vscrollbar_offset=rtk.Attribute{default=0,reflow=rtk.Widget.REFLOW_NONE,},vscrollbar_gutter=25,hscrollbar=rtk.Attribute{default=rtk.Viewport.SCROLLBAR_NEVER,calculate=rtk.Reference('vscrollbar'),},hscrollbar_offset=0,hscrollbar_gutter=25,flexw=false,flexh=true,shadow=nil,elevation=20,show_scrollbar_on_drag=false,touch_activate_delay=0,}function rtk.Viewport:initialize(attrs,...)rtk.Widget.initialize(self,attrs,self.class.attributes.defaults,...)self:_handle_attr('child', self.calc.child, nil, true)self:_handle_attr('bg', self.calc.bg)self._backingstore=nil self._needs_clamping=false self._last_draw_scroll_left=nil self._last_draw_scroll_top=nil self._vscrollx=0 self._vscrolly=0 self._vscrollh=0 self._vscrolla={current=self.calc.vscrollbar==rtk.Viewport.SCROLLBAR_ALWAYS and 0.1 or 0,target=0,}self._vscroll_in_gutter=false end function rtk.Viewport:_handle_attr(attr,value,oldval,trigger,reflow,sync)local ok=rtk.Widget._handle_attr(self,attr,value,oldval,trigger,reflow,sync)if ok==false then return ok end if attr=='child' then if oldval then oldval:_unrealize()oldval.viewport=nil oldval.parent=nil oldval.window=nil self:_sync_child_refs(oldval, 'remove')if rtk.focused==oldval then self:_set_focused_child(nil)end end if value then value.viewport=self value.parent=self value.window=self.window self:_sync_child_refs(value, 'add')if rtk.focused==value then self:_set_focused_child(value)end end elseif attr=='bg' then value=value or rtk.theme.bg local luma=rtk.color.luma(value)local offset=math.max(0,1-(1.5-3*luma)^2)self._scrollbar_alpha_proximity=0.16*(1+offset^0.2)self._scrollbar_alpha_hover=0.40*(1+offset^0.4)self._scrollbar_color=luma < 0.5 and '#ffffff' or '#000000'elseif attr=='shadow' then self._shadow=nil elseif attr == 'scroll_top' or attr == 'scroll_left' then self._needs_clamping=true end return true end function rtk.Viewport:_sync_child_refs(child,action)return rtk.Container._sync_child_refs(self,child,action)end function rtk.Viewport:_set_focused_child(child)return rtk.Container._set_focused_child(self,child)end function rtk.Viewport:focused(event)return rtk.Container.focused(self,event)end function rtk.Viewport:remove()self:attr('child', nil)end function rtk.Viewport:_reflow(boxx,boxy,boxw,boxh,fillw,fillh,clampw,clamph,uiscale,viewport,window,greedyw,greedyh)local calc=self.calc calc.x,calc.y=self:_get_box_pos(boxx,boxy)local w,h,tp,rp,bp,lp,minw,maxw,minh,maxh=self:_get_content_size(boxw,boxh,fillw,fillh,clampw,clamph,nil,greedyw,greedyh )local hpadding=lp+rp local vpadding=tp+bp local inner_maxw=rtk.clamp(w or(boxw-hpadding),minw,maxw)local inner_maxh=rtk.clamp(h or(boxh-vpadding),minh,maxh)local scrollw,scrollh=0,0 if calc.vscrollbar==rtk.Viewport.SCROLLBAR_ALWAYS or (calc.vscrollbar==rtk.Viewport.SCROLLBAR_AUTO and self._vscrollh>0)then scrollw=calc.scrollbar_size*rtk.scale.value inner_maxw=inner_maxw-scrollw end if calc.hscrollbar==rtk.Viewport.SCROLLBAR_ALWAYS or (calc.hscrollbar==rtk.Viewport.SCROLLBAR_AUTO and self._hscrollh>0)then scrollh=calc.scrollbar_size*rtk.scale.value inner_maxh=inner_maxh-scrollh end local child=calc.child local innerw,innerh local hmargin,vmargin local ccalc if child and child.visible==true then ccalc=child.calc hmargin=ccalc.lmargin+ccalc.rmargin vmargin=ccalc.tmargin+ccalc.bmargin inner_maxw=inner_maxw-hmargin inner_maxh=inner_maxh-vmargin local wx,wy,ww,wh=self:_reflow_child(inner_maxw,inner_maxh,uiscale,window,greedyw,greedyh)local pass2=false if calc.vscrollbar==rtk.Viewport.SCROLLBAR_AUTO then if scrollw==0 and wh>inner_maxh then scrollw=calc.scrollbar_size*rtk.scale.value inner_maxw=inner_maxw-scrollw pass2=ww>inner_maxw elseif scrollw>0 and wh<=inner_maxh then pass2=ww>=inner_maxw inner_maxw=inner_maxw+scrollw scrollw=0 end end if pass2 then wx,wy,ww,wh=self:_reflow_child(inner_maxw,inner_maxh,uiscale,window,greedyw,greedyh)end if greedyw then if calc.halign==rtk.Widget.CENTER then wx=wx+math.max(0,inner_maxw-ccalc.w)/2 elseif calc.halign==rtk.Widget.RIGHT then wx=wx+math.max(0,(inner_maxw-ccalc.w)-rp)end end if greedyh then if calc.valign==rtk.Widget.CENTER then wy=wy+math.max(0,inner_maxh-ccalc.h)/2 elseif calc.valign==rtk.Widget.BOTTOM then wy=wy+math.max(0,(inner_maxh-ccalc.h)-bp)end end ccalc.x=wx ccalc.y=wy child:_realize_geometry()innerw=math.ceil(rtk.clamp(ww+wx,fillw and greedyw and inner_maxw,inner_maxw))innerh=math.ceil(rtk.clamp(wh+wy,fillh and greedyh and inner_maxh,inner_maxh))else innerw,innerh=inner_maxw,inner_maxh hmargin,vmargin=0,0 end calc.w=rtk.clamp((w or(innerw+scrollw+hmargin))+hpadding,minw,maxw)calc.h=rtk.clamp((h or(innerh+scrollh+vmargin))+vpadding,minh,maxh)if not self._backingstore then self._backingstore=rtk.Image(innerw,innerh)else self._backingstore:resize(innerw,innerh,false)end self._vscrollh=0 self._needs_clamping=true if ccalc then self._scroll_clamp_left=math.max(0,ccalc.w-calc.w+lp+rp+ccalc.lmargin+ccalc.rmargin)self._scroll_clamp_top=math.max(0,ccalc.h-calc.h+tp+bp+ccalc.tmargin+ccalc.bmargin)end end function rtk.Viewport:_reflow_child(maxw,maxh,uiscale,window,greedyw,greedyh)local calc=self.calc return calc.child:reflow(0,0,maxw,maxh,false,false,not calc.flexw,not calc.flexh,uiscale,self,window,greedyw,greedyh )end function rtk.Viewport:_realize_geometry()local calc=self.calc local tp,rp,bp,lp=self:_get_padding_and_border()if self.child then local innerh=self._backingstore.h local ch=self.child.calc.h if ch>innerh then self._vscrollx=calc.x+calc.w-calc.scrollbar_size*rtk.scale.value-calc.vscrollbar_offset self._vscrolly=calc.y+calc.h*calc.scroll_top/ch+tp self._vscrollh=calc.h*innerh/ch end end if self.shadow then if not self._shadow then self._shadow=rtk.Shadow(calc.shadow)end self._shadow:set_rectangle(calc.w,calc.h,calc.elevation)end self._pre={tp=tp,rp=rp,bp=bp,lp=lp}end function rtk.Viewport:_unrealize()self._backingstore=nil if self.child then self.child:_unrealize()end end function rtk.Viewport:_draw(offx,offy,alpha,event,clipw,cliph,cltargetx,cltargety,parentx,parenty)rtk.Widget._draw(self,offx,offy,alpha,event,clipw,cliph,cltargetx,cltargety,parentx,parenty)local calc=self.calc local pre=self._pre self.cltargetx=cltargetx self.cltargety=cltargety local x=calc.x+offx+pre.lp local y=calc.y+offy+pre.tp local lastleft,lasttop local scrolled=calc.scroll_left~=self._last_draw_scroll_left or calc.scroll_top~=self._last_draw_scroll_top if scrolled then lastleft,lasttop=self._last_draw_scroll_left or 0,self._last_draw_scroll_top or 0 if self:onscrollpre(lastleft,lasttop,event)==false then calc.scroll_left=lastleft or 0 calc.scroll_top=lasttop scrolled=false else self._last_draw_scroll_left=calc.scroll_left self._last_draw_scroll_top=calc.scroll_top end end if y+calc.h<0 or y>cliph or calc.ghost then return false end self:_handle_drawpre(offx,offy,alpha,event)self:_draw_bg(offx,offy,1.0,event)local child=calc.child if child and child.realized then self:_clamp()x=x+child.calc.lmargin y=y+child.calc.tmargin self._backingstore:blit{src=gfx.dest,sx=x,sy=y,mode=rtk.Image.FAST_BLIT}self._backingstore:pushdest()child:_draw(-calc.scroll_left,-calc.scroll_top,1.0,event,calc.w,calc.h,cltargetx+x,cltargety+y,0,0 )child:_draw_debug_box(-calc.scroll_left,-calc.scroll_top,event)self._backingstore:popdest()self._backingstore:blit{dx=x,dy=y,alpha=alpha*calc.alpha}self:_draw_scrollbars(offx,offy,cltargetx,cltargety,alpha,event)end if calc.shadow then self._shadow:draw(calc.x+offx,calc.y+offy,alpha*calc.alpha)end self:_draw_borders(offx,offy,alpha)if scrolled then self:onscroll(lastleft,lasttop,event)end self:_handle_draw(offx,offy,alpha,event)end function rtk.Viewport:_draw_scrollbars(offx,offy,cltargetx,cltargety,alpha,event)if self._vscrolla.current==0 or self._vscrollh==0 then return end local calc=self.calc local scrx=offx+self._vscrollx local scry=offy+calc.y+calc.h*calc.scroll_top/self.child.calc.h self:setcolor(self._scrollbar_color,self._vscrolla.current*alpha)gfx.rect(scrx,scry,calc.scrollbar_size*rtk.scale.value,self._vscrollh+1,1)end function rtk.Viewport:_calc_scrollbar_alpha(clparentx,clparenty,event,dragchild)local calc=self.calc if calc.vscrollbar==rtk.Viewport.SCROLLBAR_NEVER then return end local dragself=(rtk.dnd.dragging==self)local alpha=0 local duration=0.2 if self._vscrollh>0 then if not rtk._modal or rtk.is_modal(self)then local overthumb=event:get_button_state(self.id)if self.mouseover then if overthumb==nil and self._vscroll_in_gutter then overthumb=rtk.point_in_box(event.x,event.y,clparentx+self._vscrollx,clparenty+calc.y+calc.h*calc.scroll_top/self.child.calc.h,calc.scrollbar_size*rtk.scale.value,self._vscrollh )end if event.type==rtk.Event.MOUSEDOWN then event:set_button_state(self.id,overthumb)end end if self._vscroll_in_gutter or dragself then if overthumb then alpha=self._scrollbar_alpha_hover duration=0.1 else alpha=self._scrollbar_alpha_proximity end elseif self.mouseover or calc.vscrollbar==rtk.Viewport.SCROLLBAR_ALWAYS then alpha=self._scrollbar_alpha_proximity elseif dragchild and dragchild.show_scrollbar_on_drag then alpha=self._scrollbar_alpha_proximity duration=0.15 end elseif calc.vscrollbar==rtk.Viewport.SCROLLBAR_ALWAYS then alpha=self._scrollbar_alpha_proximity end end if alpha~=self._vscrolla.target then if alpha==0 then duration=0.3 end rtk.queue_animation{key=string.format('%s.vscrollbar', self.id),src=self._vscrolla.current,dst=alpha,duration=duration,update=function(value)self._vscrolla.current=value self:queue_draw()end,}self._vscrolla.target=alpha self:queue_draw()end end function rtk.Viewport:_handle_event(clparentx,clparenty,event,clipped,listen)local calc=self.calc local pre=self._pre listen=self:_should_handle_event(listen)local x=calc.x+clparentx local y=calc.y+clparenty local hovering=rtk.point_in_box(event.x,event.y,x,y,calc.w,calc.h)and self.window.in_window local dragging=rtk.dnd.dragging local is_child_dragging=dragging and dragging.viewport==self local child=self.child if event.type==rtk.Event.MOUSEMOVE then self._vscroll_in_gutter=false if listen and is_child_dragging and dragging.scroll_on_drag then if event.y-20y+calc.h then self:scrollby(0,math.max(5,math.abs(y+calc.h-event.y)),false)end elseif listen and not dragging and not event.handled and hovering then if calc.vscrollbar~=rtk.Viewport.SCROLLBAR_NEVER and self._vscrollh>0 then local gutterx=self._vscrollx+clparentx-calc.vscrollbar_gutter local guttery=calc.y+clparenty if rtk.point_in_box(event.x,event.y,gutterx,guttery,calc.vscrollbar_gutter+calc.scrollbar_size*rtk.scale.value,calc.h)then self._vscroll_in_gutter=true if event.x>=self._vscrollx+clparentx then event:set_handled(self)end end end end elseif listen and not event.handled and event.type==rtk.Event.MOUSEDOWN then if not self:cancel_animation('scroll_top') then self:_reset_touch_scroll()end if self._vscroll_in_gutter and event.x>=self._vscrollx+clparentx then local scrolly=self:_get_vscrollbar_client_pos()if event.yscrolly+self._vscrollh then self:_handle_scrollbar(event,nil,self._vscrollh/2,true)end event:set_handled(self)end end if(not event.handled or event.type==rtk.Event.MOUSEMOVE)and not(event.type==rtk.Event.MOUSEMOVE and self.window:_is_touch_scrolling(self))and child and child.visible and child.realized then self:_clamp()child:_handle_event(x-calc.scroll_left+pre.lp+child.calc.lmargin,y-calc.scroll_top+pre.tp+child.calc.tmargin,event,clipped or not hovering,listen )end if listen and hovering and not event.handled and event.type==rtk.Event.MOUSEWHEEL then if child and self._vscrollh>0 and event.wheel~=0 then local distance=event.wheel*math.min(calc.h/2,120)self:scrollby(0,distance)event:set_handled(self)end end listen=rtk.Widget._handle_event(self,clparentx,clparenty,event,clipped,listen)self.mouseover=self.mouseover or(child and child.mouseover)self:_calc_scrollbar_alpha(clparentx,clparenty,event,is_child_dragging and dragging)return listen end function rtk.Viewport:_get_vscrollbar_client_pos()local calc=self.calc return self.clienty+calc.h*calc.scroll_top/self.child.calc.h end function rtk.Viewport:_handle_scrollbar(event,hoffset,voffset,gutteronly,natural)local calc=self.calc local pre=self._pre if voffset~=nil then self:cancel_animation('scroll_top')if gutteronly then local ssy=self:_get_vscrollbar_client_pos()if event.y>=ssy and event.y<=ssy+self._vscrollh then return false end end local target if natural then target=calc.scroll_top+(voffset-event.y)else local pct=rtk.clamp(event.y-self.clienty-voffset,0,calc.h)/calc.h target=pct*(self.child.calc.h)end self:scrollto(calc.scroll_left,target,false)end end function rtk.Viewport:_handle_dragstart(event,x,y,t)local draggable,droppable=self:ondragstart(self,event,x,y,t)if draggable~=nil then return draggable,droppable end if math.abs(y-event.y)>0 then if self._vscroll_in_gutter and event.x>=self._vscrollx+self.offx+self.cltargetx then return {true,y-self:_get_vscrollbar_client_pos(),nil,false},false elseif rtk.touchscroll and event.buttons&rtk.mouse.BUTTON_LEFT~=0 and self._vscrollh>0 then self.window:_set_touch_scrolling(self,true)return {true,y,{{x,y,t}},true},false end end return false,false end function rtk.Viewport:_handle_dragmousemove(event,arg)local ok=rtk.Widget._handle_dragmousemove(self,event)if ok==false or event.simulated then return ok end local vscrollbar,lasty,samples,natural=table.unpack(arg)if vscrollbar then self:_handle_scrollbar(event,nil,lasty,false,natural)if natural then arg[2]=event.y samples[#samples+1]={event.x,event.y,event.time}end self.window:request_mouse_cursor(rtk.mouse.cursors.POINTER,true)end return true end function rtk.Viewport:_reset_touch_scroll()if self.window then self.window:_set_touch_scrolling(self,false)end end function rtk.Viewport:_handle_dragend(event,arg)local ok=rtk.Widget._handle_dragend(self,event)if ok==false then return ok end local vscrollbar,lasty,samples,natural=table.unpack(arg)if natural then local now=event.time local x1,y1,t1=event.x,event.y,event.time for i=#samples,1,-1 do local x,y,t=table.unpack(samples[i])if now-t>0.2 then break end x1,y1,t1=x,y,t end local v=0 if t1~=event.time then v=(event.y-y1)-(event.time-t1)end local distance=v*rtk.scale.value local x,y=self:_get_clamped_scroll(self.calc.scroll_left,self.calc.scroll_top-distance)local duration=1 self:animate{attr='scroll_top', dst=y, duration=duration, easing='out-cubic'}:done(function()self:_reset_touch_scroll()end):cancelled(function()self:_reset_touch_scroll()end)end self:queue_draw()event:set_handled(self)return true end function rtk.Viewport:_scrollto(x,y,smooth,animx,animy)local calc=self.calc if not smooth or not self.realized then x=x or self.scroll_left y=y or self.scroll_top if x==calc.scroll_left and y==calc.scroll_top then return end self._needs_clamping=true calc.scroll_left=x calc.scroll_top=y self.scroll_left=calc.scroll_left self.scroll_top=calc.scroll_top self:queue_draw()else x,y=self:_get_clamped_scroll(x or calc.scroll_left,y or calc.scroll_top)animx=animx or self:get_animation('scroll_left')animy=animy or self:get_animation('scroll_top')if calc.scroll_left~=x and(not animx or animx.dst~=x)then self:animate{attr='scroll_left', dst=x, duration=0.15}end if calc.scroll_top~=y and(not animy or animy.dst~=y)then self:animate{attr='scroll_top', dst=y, duration=0.2, easing='out-sine'}end end end function rtk.Viewport:_get_smoothscroll(override)if override~=nil then return override end local calc=self.calc if calc.smoothscroll~=nil then return calc.smoothscroll end return rtk.smoothscroll end function rtk.Viewport:scrollto(x,y,smooth)self:_scrollto(x,y,self:_get_smoothscroll(smooth))end function rtk.Viewport:scrollby(offx,offy,smooth)local calc=self.calc local x,y,animx,animy smooth=self:_get_smoothscroll(smooth)if smooth then animx=self:get_animation('scroll_left')animy=self:get_animation('scroll_top')x=(animx and animx.dst or calc.scroll_left)+(offx or 0)y=(animy and animy.dst or calc.scroll_top)+(offy or 0)else x=calc.scroll_left+(offx or 0)y=calc.scroll_top+(offy or 0)end self:_scrollto(x,y,smooth,animx,animy)end function rtk.Viewport:scrollable()if not self.child then return false end local vcalc=self.calc local ccalc=self.child.calc return ccalc.w>vcalc.w or ccalc.h>vcalc.h end function rtk.Viewport:_get_clamped_scroll(left,top)return rtk.clamp(left,0,self._scroll_clamp_left),rtk.clamp(top,0,self._scroll_clamp_top)end function rtk.Viewport:_clamp()if self._needs_clamping then local calc=self.calc calc.scroll_left,calc.scroll_top=self:_get_clamped_scroll(self.scroll_left,self.scroll_top)self.scroll_left,self.scroll_top=calc.scroll_left,calc.scroll_top self._needs_clamping=false end end function rtk.Viewport:onscrollpre(last_left,last_top,event)end function rtk.Viewport:onscroll(last_left,last_top,event)end end)() __mod_rtk_popup=(function() local rtk=__mod_rtk_core rtk.Popup=rtk.class('rtk.Popup', rtk.Viewport)rtk.Popup.AUTOCLOSE_DISABLED=0 rtk.Popup.AUTOCLOSE_LOCAL=1 rtk.Popup.AUTOCLOSE_GLOBAL=2 rtk.Popup.register{anchor=rtk.Attribute{reflow=rtk.Widget.REFLOW_FULL},margin=rtk.Attribute{default=20,reflow=rtk.Widget.REFLOW_FULL,},width_from_anchor=rtk.Attribute{default=true,reflow=rtk.Widget.REFLOW_FULL,},overlay=rtk.Attribute{default=function(self,attr)return rtk.theme.popup_overlay end,calculate=rtk.Reference('bg'),},autoclose=rtk.Attribute{default=rtk.Popup.AUTOCLOSE_LOCAL,calculate={['disabled']=rtk.Popup.AUTOCLOSE_DISABLED,['local']=rtk.Popup.AUTOCLOSE_LOCAL,['global']=rtk.Popup.AUTOCLOSE_GLOBAL,[true]=rtk.Popup.AUTOCLOSE_LOCAL,[false]=rtk.Popup.AUTOCLOSE_DISABLED,}},opened=false,bg=rtk.Attribute{default=function(self,attr)return rtk.theme.popup_bg or {rtk.color.mod(rtk.theme.bg,1,1,rtk.theme.popup_bg_brightness,0.96)}end,},border=rtk.Attribute{default=function(self,attr)return rtk.theme.popup_border end,},shadow=rtk.Attribute{default=function()return rtk.theme.popup_shadow end,},visible=false,elevation=35,padding=10,z=1000,}function rtk.Popup:initialize(attrs,...)rtk.Viewport.initialize(self,attrs,self.class.attributes.defaults,...)self._popup_visible=false end function rtk.Popup:_handle_event(clparentx,clparenty,event,clipped,listen)listen=rtk.Viewport._handle_event(self,clparentx,clparenty,event,clipped,listen)if event.type==rtk._touch_activate_event and self.mouseover then event:set_handled(self)end return listen end function rtk.Popup:_reflow(boxx,boxy,boxw,boxh,fillw,fillh,clampw,clamph,uiscale,viewport,window,greedyw,greedyh)local calc=self.calc local anchor=calc.anchor if anchor then local y=anchor.clienty local wh=self.window.calc.h if y0 then for i=1,#attrs do local w=attrs[i] self:add(w)end end end function rtk.Container:_handle_mouseenter(event)local ret=self:onmouseenter(event)if ret~=false then if self.bg or self.autofocus then return true end end return ret end function rtk.Container:_handle_mousemove(event)local ret=rtk.Widget._handle_mousemove(self,event)if ret~=false and self.hovering then event:set_handled(self)return true end return ret end function rtk.Container:_draw_debug_box(offx,offy,event)if not rtk.Widget._draw_debug_box(self,offx,offy,event)then return end gfx.set(1,1,1,1)for i=1,#self.children do local widget,attrs=table.unpack(self.children[i])local cb=attrs._cellbox if cb and widget.visible then gfx.rect(offx+self.calc.x+cb[1],offy+self.calc.y+cb[2],cb[3],cb[4],0)end end end function rtk.Container:_sync_child_refs(child,action)if child.refs and not child.refs.__empty then if action=='add' then local w=self while w do for k,v in pairs(child.refs)do if k ~= '__self' and k ~= '__empty' then w.refs[k]=v end end w=w.parent end else for k in pairs(child.refs)do self.refs[k]=nil end end end end function rtk.Container:_set_focused_child(child)local w=self while w do w._focused_child=child w=w.parent end end function rtk.Container:_validate_child(child)assert(rtk.isa(child, rtk.Widget), 'object being added to container is not subclassed from rtk.Widget')end function rtk.Container:_reparent_child(child)self:_validate_child(child)if child.parent and child.parent~=self then child.parent:remove(child)end child.parent=self child.window=self.window self:_sync_child_refs(child, 'add')if rtk.focused==child then self:_set_focused_child(child)end end function rtk.Container:_unparent_child(pos)local child=self.children[pos][1] if child then if child.visible then child:_unrealize()end child.parent=nil child.window=nil self:_sync_child_refs(child, 'remove')if rtk.focused==child then self:_set_focused_child(nil)end return child end end function rtk.Container:focused(event)return rtk.focused==self or(event and event.type==rtk.Event.KEY and rtk.focused and rtk.focused==self._focused_child )end function rtk.Container:add(widget,attrs)self:_reparent_child(widget)self.children[#self.children+1]={widget,self:_calc_cell_attrs(widget,attrs)}self._child_index_by_id=nil self:queue_reflow(rtk.Widget.REFLOW_FULL)return widget end function rtk.Container:update(widget,attrs,merge)local n=self:get_child_index(widget)assert(n, 'Widget not found in container')attrs=self:_calc_cell_attrs(widget,attrs)if merge then local cellattrs=self.children[n][2] table.merge(cellattrs,attrs)else self.children[n][2]=attrs end self:queue_reflow(rtk.Widget.REFLOW_FULL)end function rtk.Container:insert(pos,widget,attrs)self:_reparent_child(widget)table.insert(self.children,pos,{widget,self:_calc_cell_attrs(widget,attrs)})self._child_index_by_id=nil self:queue_reflow(rtk.Widget.REFLOW_FULL)end function rtk.Container:replace(index,widget,attrs)if index<=0 or index>#self.children then return end local prev=self:_unparent_child(index)self:_reparent_child(widget)self.children[index]={widget,self:_calc_cell_attrs(widget,attrs)}self._child_index_by_id=nil self:queue_reflow(rtk.Widget.REFLOW_FULL)return prev end function rtk.Container:remove_index(index)if index<=0 or index>#self.children then return end local child=self:_unparent_child(index)table.remove(self.children,index)self._child_index_by_id=nil self:queue_reflow(rtk.Widget.REFLOW_FULL)return child end function rtk.Container:remove(widget)local n=self:get_child_index(widget)if n~=nil then self:remove_index(n)return n end end function rtk.Container:remove_all()for i=1,#self.children do local widget=self.children[i][1] if widget and widget.visible then widget:_unrealize()end end self.children={}self._child_index_by_id=nil self:queue_reflow(rtk.Widget.REFLOW_FULL)end function rtk.Container:_calc_cell_attrs(widget,attrs)attrs=attrs or widget.cell if not attrs then return {}end local keys=table.keys(attrs)local calculated={}for n=1,#keys do local k=keys[n] calculated[k]=self:_calc_attr(k, attrs[k], calculated, nil, 'cell', widget)end return calculated end function rtk.Container:_reorder(srcidx,targetidx)if srcidx~=nil and srcidx~=targetidx then local widgetattrs=table.remove(self.children,srcidx)table.insert(self.children,rtk.clamp(targetidx,1,#self.children+1),widgetattrs)self._child_index_by_id=nil self:queue_reflow(rtk.Widget.REFLOW_FULL)return true else return false end end function rtk.Container:reorder(widget,targetidx)local srcidx=self:get_child_index(widget)return self:_reorder(srcidx,targetidx)end function rtk.Container:reorder_before(widget,target)local srcidx=self:get_child_index(widget)local targetidx=self:get_child_index(target)if not srcidx or not targetidx then return false end return self:_reorder(srcidx,targetidx>srcidx and targetidx-1 or targetidx)end function rtk.Container:reorder_after(widget,target)local srcidx=self:get_child_index(widget)local targetidx=self:get_child_index(target)if not srcidx or not targetidx then return false end return self:_reorder(srcidx,srcidx>targetidx and targetidx+1 or targetidx)end function rtk.Container:get_child(idx)if idx<0 then idx=#self.children+idx+1 end local child=self.children[idx] if child then return child[1] end end function rtk.Container:get_child_index(widget)if not self._child_index_by_id then local cache={}for i=1,#self.children do local widgetattrs=self.children[i] if widgetattrs and widgetattrs[1].id then cache[widgetattrs[1].id]=i end end self._child_index_by_id=cache end return self._child_index_by_id[widget.id] end function rtk.Container:_handle_event(clparentx,clparenty,event,clipped,listen)local calc=self.calc local x=calc.x+clparentx local y=calc.y+clparenty self.clientx,self.clienty=x,y listen=self:_should_handle_event(listen)if y+calc.h<0 or y>self.window.calc.h or calc.ghost then return false end local chmouseover local zs=self._z_indexes for zidx=#zs,1,-1 do local zchildren=self._reflowed_children[zs[zidx]] local nzchildren=zchildren and #zchildren or 0 for cidx=nzchildren,1,-1 do local widget,attrs=table.unpack(zchildren[cidx])if widget and widget.realized and widget.parent then local wx,wy if widget.calc.position&rtk.Widget.POSITION_FIXED~=0 and self.viewport then local vcalc=self.viewport.calc wx,wy=x+vcalc.scroll_left,y+vcalc.scroll_top else wx,wy=x,y end self:_handle_event_child(clparentx,clparenty,event,clipped,listen,wx,wy,widget,attrs)chmouseover=chmouseover or widget.mouseover end end end listen=rtk.Widget._handle_event(self,clparentx,clparenty,event,clipped,listen)self.mouseover=self.mouseover or chmouseover return listen end function rtk.Container:_handle_event_child(clparentx,clparenty,event,clipped,listen,wx,wy,child,attrs)return child:_handle_event(wx,wy,event,clipped,listen)end function rtk.Container:_add_reflowed_child(widgetattrs,z)local z_children=self._reflowed_children[z] if z_children then z_children[#z_children+1]=widgetattrs else self._reflowed_children[z]={widgetattrs}end end function rtk.Container:_determine_zorders()local zs={}for z in pairs(self._reflowed_children)do zs[#zs+1]=z end table.sort(zs)self._z_indexes=zs end function rtk.Container:_get_cell_padding(widget,attrs)local calc=widget.calc local scale=rtk.scale.value return ((attrs.tpadding or 0)+(calc.tmargin or 0))*scale,((attrs.rpadding or 0)+(calc.rmargin or 0))*scale,((attrs.bpadding or 0)+(calc.bmargin or 0))*scale,((attrs.lpadding or 0)+(calc.lmargin or 0))*scale end function rtk.Container:_set_cell_box(attrs,x,y,w,h)attrs._cellbox={math.round(x),math.round(y),math.round(w),math.round(h)}end function rtk.Container:_reflow(boxx,boxy,boxw,boxh,fillw,fillh,clampw,clamph,uiscale,viewport,window,greedyw,greedyh)local calc=self.calc local x,y=self:_get_box_pos(boxx,boxy)local w,h,tp,rp,bp,lp,minw,maxw,minh,maxh=self:_get_content_size(boxw,boxh,fillw and greedyw,fillh and greedyh,clampw,clamph,nil,greedyw,greedyh )local inner_maxw=rtk.clamp(w or(boxw-lp-rp),minw,maxw)local inner_maxh=rtk.clamp(h or(boxh-tp-bp),minh,maxh)local innerw=w or 0 local innerh=h or 0 clampw=clampw or w~=nil or fillw clamph=clamph or h~=nil or fillh self._reflowed_children={}self._child_index_by_id={}for n,widgetattrs in ipairs(self.children)do local widget,attrs=table.unpack(widgetattrs)local wcalc=widget.calc attrs._cellbox=nil self._child_index_by_id[widget.id]=n if widget.visible==true then local ctp,crp,cbp,clp=self:_get_cell_padding(widget,attrs)attrs._minw=self:_adjscale(attrs.minw,uiscale,greedyw and inner_maxw)attrs._maxw=self:_adjscale(attrs.maxw,uiscale,greedyh and inner_maxw)attrs._minh=self:_adjscale(attrs.minh,uiscale,greedyh and inner_maxh)attrs._maxh=self:_adjscale(attrs.maxh,uiscale,greedyh and inner_maxh)local wx,wy,ww,wh=widget:reflow(0,0,rtk.clamp(inner_maxw-widget.x-clp-crp,attrs._minw,attrs._maxw),rtk.clamp(inner_maxh-widget.y-ctp-cbp,attrs._minh,attrs._maxh),attrs.fillw,attrs.fillh,clampw or attrs.maxw~=nil,clamph or attrs.maxh~=nil,uiscale,viewport,window,greedyw,greedyh )ww=math.max(ww,attrs._minw or 0)wh=math.max(wh,attrs._minh or 0)attrs._halign=attrs.halign or calc.halign attrs._valign=attrs.valign or calc.valign if not attrs._halign or attrs._halign==rtk.Widget.LEFT or not greedyw then wx=lp+clp elseif attrs._halign==rtk.Widget.CENTER then wx=lp+clp+math.max(0,(math.min(innerw,inner_maxw)-ww-clp-crp)/2)else wx=lp+math.max(0,math.min(innerw,inner_maxw)-ww-crp)end if not attrs._valign or attrs._valign==rtk.Widget.TOP or not greedyh then wy=tp+ctp elseif attrs._valign==rtk.Widget.CENTER then wy=tp+ctp+math.max(0,(math.min(innerh,inner_maxh)-wh-ctp-cbp)/2)else wy=tp+math.max(0,math.min(innerh,inner_maxh)-wh-cbp)end wcalc.x=wcalc.x+wx widget.box[1]=wx wcalc.y=wcalc.y+wy widget.box[2]=wy self:_set_cell_box(attrs,wcalc.x,wcalc.y,ww+clp+crp,wh+ctp+cbp)widget:_realize_geometry()innerw=math.max(innerw,wcalc.x+ww-lp+crp)innerh=math.max(innerh,wcalc.y+wh-tp+cbp)self:_add_reflowed_child(widgetattrs,attrs.z or wcalc.z or 0)else widget.realized=false end end self:_determine_zorders()calc.x=x calc.y=y calc.w=math.ceil(rtk.clamp((w or innerw)+lp+rp,minw,maxw))calc.h=math.ceil(rtk.clamp((h or innerh)+tp+bp,minh,maxh))end function rtk.Container:_draw(offx,offy,alpha,event,clipw,cliph,cltargetx,cltargety,parentx,parenty)local calc=self.calc rtk.Widget._draw(self,offx,offy,alpha,event,clipw,cliph,cltargetx,cltargety,parentx,parenty)local x,y=calc.x+offx,calc.y+offy if y+calc.h<0 or y>cliph or calc.ghost then return false end local wpx=parentx+calc.x local wpy=parenty+calc.y self:_handle_drawpre(offx,offy,alpha,event)self:_draw_bg(offx,offy,alpha,event)local child_alpha=alpha*self.alpha for _,z in ipairs(self._z_indexes)do for _,widgetattrs in ipairs(self._reflowed_children[z])do local widget,attrs=table.unpack(widgetattrs)if attrs.bg and attrs._cellbox then local cb=attrs._cellbox self:setcolor(attrs.bg,child_alpha)gfx.rect(x+cb[1],y+cb[2],cb[3],cb[4],1)end if widget and widget.realized then local wx,wy=x,y if widget.calc.position&rtk.Widget.POSITION_FIXED~=0 then wx,wy=wpx,wpy end widget:_draw(wx,wy,child_alpha,event,clipw,cliph,cltargetx,cltargety,wpx,wpy)widget:_draw_debug_box(wx,wy,event)end end end self:_draw_borders(offx,offy,alpha)self:_handle_draw(offx,offy,alpha,event)end function rtk.Container:_unrealize()rtk.Widget._unrealize(self)for i=1,#self.children do local widget=self.children[i][1] if widget and widget.realized then widget:_unrealize()end end end end)() __mod_rtk_window=(function() local rtk=__mod_rtk_core local log=__mod_rtk_log rtk.Window=rtk.class('rtk.Window', rtk.Container)rtk.Window.static.DOCK_BOTTOM=(function()return {0} end)()rtk.Window.static.DOCK_LEFT=(function()return {1} end)()rtk.Window.static.DOCK_TOP=(function()return {2} end)()rtk.Window.static.DOCK_RIGHT=(function()return {3} end)()rtk.Window.static.DOCK_FLOATING=(function()return {4} end)()function rtk.Window.static._make_icons()local w,h=12,12 local sz=2 local icon=rtk.Image(w,h)icon:pushdest()rtk.color.set(rtk.theme.dark and {1,1,1,1} or {0,0,0,1})for row=0,2 do for col=0,2 do local n=row*3+col if n==2 or n>=4 then gfx.rect(2*col*sz,2*row*sz,sz,sz,1)end end end icon:popdest()rtk.Window.static._icon_resize_grip=icon end rtk.Window.register{x=rtk.Attribute{type='number',default=rtk.Attribute.NIL,reflow=rtk.Widget.REFLOW_NONE,redraw=false,window_sync=true,},y=rtk.Attribute{type='number',default=rtk.Attribute.NIL,reflow=rtk.Widget.REFLOW_NONE,redraw=false,window_sync=true,},w=rtk.Attribute{priority=true,type='number',window_sync=true,reflow_uses_exterior_value=true,animate=function(self,anim)return rtk.Widget.attributes.w.animate(self,anim,rtk.scale.framebuffer)end,calculate=function(self,attr,value,target)return value and value*rtk.scale.framebuffer or target[attr] end,},h=rtk.Attribute{priority=true,type='number',window_sync=true,reflow_uses_exterior_value=true,animate=rtk.Reference('w'),calculate=rtk.Reference('w'),},minw=rtk.Attribute{default=100,window_sync=true,reflow_uses_exterior_value=true,},minh=rtk.Attribute{default=30,window_sync=true,reflow_uses_exterior_value=true,},maxw=rtk.Attribute{window_sync=true,reflow_uses_exterior_value=true,},maxh=rtk.Attribute{window_sync=true,reflow_uses_exterior_value=true,},visible=rtk.Attribute{window_sync=true,},docked=rtk.Attribute{default=false,window_sync=true,reflow=rtk.Widget.REFLOW_NONE,},dock=rtk.Attribute{default=rtk.Window.DOCK_RIGHT,calculate={bottom=rtk.Window.DOCK_BOTTOM,left=rtk.Window.DOCK_LEFT,top=rtk.Window.DOCK_TOP,right=rtk.Window.DOCK_RIGHT,floating=rtk.Window.DOCK_FLOATING },window_sync=true,reflow=rtk.Widget.REFLOW_NONE,},pinned=rtk.Attribute{default=false,window_sync=true,calculate=function(self,attr,value,target)return rtk.has_js_reascript_api and value end,},borderless=rtk.Attribute{default=false,window_sync=true,calculate=rtk.Reference('pinned')},title=rtk.Attribute{default='REAPER application',reflow=rtk.Widget.REFLOW_NONE,window_sync=true,redraw=false,},opacity=rtk.Attribute{default=1.0,reflow=rtk.Widget.REFLOW_NONE,window_sync=true,redraw=false,},resizable=rtk.Attribute{default=true,reflow=rtk.Widget.REFLOW_NONE,window_sync=true,},hwnd=nil,in_window=false,is_focused=not rtk.has_js_reascript_api and true or false,running=false,cursor=rtk.mouse.cursors.POINTER,scalability=rtk.Widget.BOX,}function rtk.Window:initialize(attrs,...)rtk.Container.initialize(self,attrs,self.class.attributes.defaults,...)rtk.window=self self.window=self if self.id==0 and self.calc.bg and rtk.theme.default then rtk.set_theme_by_bgcolor(self.calc.bg)end if rtk.Window.static._icon_resize_grip==nil then rtk.Window._make_icons()end if not rtk.has_js_reascript_api then self:sync('borderless', false)self:sync('pinned', false)end self._dockstate=0 self._backingstore=rtk.Image()self._event=rtk.Event()self._reflow_queued=false self._reflow_widgets=nil self._blits_queued=0 self._draw_queued=false self._mouse_refresh_queued=false self._sync_window_attrs_on_update=true self._resize_grip=nil self._move_grip=nil self._os_window_frame_width=0 self._os_window_frame_height=0 self._undocked_geometry=nil self._unmaximized_geometry=nil self._last_mousemove_time=nil self._last_mouseup_time=0 self._touch_scrolling={count=0}self._last_synced_attrs={}end function rtk.Window:_handle_attr(attr,value,oldval,trigger,reflow,sync)local ok=rtk.Widget._handle_attr(self,attr,value,oldval,trigger,reflow,sync)if ok==false then return ok end if attr=='bg' then local color=rtk.color.int(value or rtk.theme.bg)gfx.clear=color if rtk.has_js_reascript_api then if self._gdi_brush then reaper.JS_GDI_DeleteObject(self._gdi_brush)reaper.JS_GDI_DeleteObject(self._gdi_pen)else reaper.atexit(function()reaper.JS_GDI_DeleteObject(self._gdi_brush)reaper.JS_GDI_DeleteObject(self._gdi_pen)end)end color=rtk.color.flip_byte_order(color)self._gdi_brush=reaper.JS_GDI_CreateFillBrush(color)self._gdi_pen=reaper.JS_GDI_CreatePen(1,color)end end if self.class.attributes.get(attr).window_sync and not sync then self._sync_window_attrs_on_update=true end return true end function rtk.Window:_get_dockstate_from_attrs()local calc=self.calc local dock=calc.dock if type(dock)=='table' then dock=self:_get_docker_at_pos(dock[1])end local dockstate=(dock or 0)<<8 if calc.docked and calc.docked~=0 then dockstate=dockstate|1 end return dockstate end function rtk.Window:_get_docker_at_pos(pos)if not reaper.DockGetPosition then return 0 end for i=1,20 do if reaper.DockGetPosition(i)==pos then return i end end end function rtk.Window:_clear_gdi(startw,starth)if not rtk.os.windows or not rtk.has_js_reascript_api or not self.hwnd then return end local calc=self.calc local dc=reaper.JS_GDI_GetWindowDC(self.hwnd)reaper.JS_GDI_SelectObject(dc,self._gdi_brush)reaper.JS_GDI_SelectObject(dc,self._gdi_pen)local x=0 local y=0 local r,w,h=reaper.JS_Window_GetClientSize(self.hwnd)if not startw then reaper.JS_GDI_FillRect(dc,x,y,w*2,h*2)elseif w>startw or h>starth then if not calc.docked and not calc.borderless then startw=startw+self._os_window_frame_width starth=starth+self._os_window_frame_height end reaper.JS_GDI_FillRect(dc,x+math.round(startw),y,w*2,h*2)reaper.JS_GDI_FillRect(dc,x,y+math.round(starth),w*2,h*2)end reaper.JS_GDI_ReleaseDC(self.hwnd,dc)end function rtk.Window:focus()if self.hwnd and rtk.has_js_reascript_api then reaper.JS_Window_SetFocus(self.hwnd)self:queue_draw()return true else return false end end function rtk.Window:_run()self:_update()if self.running then rtk.defer(self._run,self)end self._run_queued=self.running end function rtk.Window:_get_display_resolution(working,frame)local x=math.floor(self.x or 0)local y=math.floor(self.y or 0)local w=math.floor(x+(self.w or 1))local h=math.floor(y+(self.h or 1))local l,t,r,b=reaper.my_getViewport(0,0,0,0,x,y,w,h,working and 1 or 0)local sw=r-l local sh=math.abs(b-t)if frame then local borderless=self.calc.borderness sw=sw-(borderless and 0 or self._os_window_frame_width)sh=sh-(borderless and 0 or self._os_window_frame_height)end return l,t,sw,sh end function rtk.Window:_get_relative_size_from_display(w,h)local sz=w or h if sz>0 and sz<=1.0 then local _,_,sw,sh=self:_get_display_resolution(true,not self.calc.borderless)return w and sw*w or sh*h else return sz end end function rtk.Window:_get_geometry_from_attrs(overrides)overrides=overrides or {}local scale=rtk.scale.framebuffer or 1 local minw,maxw,minh,maxh,sx,sy,sw,sh=self:_get_min_max_sizes()if not sh then sx,sy,sw,sh=self:_get_display_resolution(true,not self.calc.borderless)end local calc=self.calc local x=self.x local y=self.y if not x then x=0 overrides.halign=overrides.halign or rtk.Widget.CENTER end if not y then y=0 overrides.valign=overrides.valign or rtk.Widget.CENTER end local w=rtk.isrel(self.w)and(self.w*sw)or(calc.w/scale)local h=rtk.isrel(self.h)and(self.h*sh)or(calc.h/scale)w=rtk.clamp(w,minw and minw/scale,maxw and maxw/scale)h=rtk.clamp(h,minh and minh/scale,maxh and maxh/scale)if sw and sh then if overrides.halign==rtk.Widget.LEFT then x=sx elseif overrides.halign==rtk.Widget.CENTER then x=sx+(overrides.x or 0)+(sw-w)/2 elseif overrides.halign==rtk.Widget.RIGHT then x=sx+(overrides.x or 0)+(sw-w)end if rtk.os.mac then if overrides.valign==rtk.Widget.TOP then y=sy+(overrides.y or 0)+(sh-h)elseif overrides.valign==rtk.Widget.CENTER then y=sy+(overrides.y or 0)+(sh-h)/2 elseif overrides.valign==rtk.Widget.BOTTOM then y=sy+(overrides.y or 0)end else if overrides.valign==rtk.Widget.TOP then y=sy elseif overrides.valign==rtk.Widget.CENTER then y=sy+(overrides.y or 0)+(sh-h)/2 elseif overrides.valign==rtk.Widget.BOTTOM then y=sy+(overrides.y or 0)+(sh-h)end end if overrides.constrain then x=rtk.clamp(x,sx,sx+sw-w)y=rtk.clamp(y,sy,sy+sh-h)w=rtk.clamp(w,self.minw or 0,sw-(x-sx))h=rtk.clamp(h,self.minh or 0,sh-(rtk.os.mac and y-sy-h or y-sy))end end return math.round(x),math.round(y),math.round(w),math.round(h)end function rtk.Window:_sync_window_attrs(overrides)local calc=self.calc local lastw,lasth=self.w,self.h local resized local dockstate=self:_get_dockstate_from_attrs()if not rtk.has_js_reascript_api or not self.hwnd then if dockstate~=self._dockstate then gfx.dock(dockstate)self:_handle_dock_change(dockstate)self:onresize(lastw,lasth)return 1 else return 0 end end if not self.w or not self.h then self:reflow(rtk.Widget.REFLOW_FULL)end if dockstate~=self._dockstate then gfx.dock(dockstate)local r,w,h=reaper.JS_Window_GetClientSize(self.hwnd)self:_handle_dock_change(dockstate)if calc.docked then gfx.w,gfx.h=w,h self:sync('w', w / rtk.scale.framebuffer, w)self:sync('h', h / rtk.scale.framebuffer, h)end self:onresize(lastw,lasth)return 1 end if self._resize_grip then self._resize_grip:attr('visible', calc.borderless and calc.resizable and not calc.docked)end if not calc.docked then if not calc.visible then reaper.JS_Window_Show(self.hwnd, 'HIDE')return 0 end local style='SYSMENU,DLGSTYLE,BORDER,CAPTION'if calc.resizable then style=style .. ',THICKFRAME'end if calc.borderless then style='POPUP'self:_setup_borderless()if not self.realized then local sw=math.ceil(self.calc.w/rtk.scale.framebuffer)local sh=math.ceil(self.calc.h/rtk.scale.framebuffer)reaper.JS_Window_Resize(self.hwnd,sw,sh)end end local function restyle()reaper.JS_Window_SetStyle(self.hwnd,style)if rtk.os.bits~=32 then local n=reaper.JS_Window_GetLong(self.hwnd, 'STYLE')reaper.JS_Window_SetLong(self.hwnd, 'STYLE', n | 0x80000000)end reaper.JS_Window_SetZOrder(self.hwnd, calc.pinned and 'TOPMOST' or 'NOTOPMOST')local r,x1,y1,x2,y2=reaper.JS_Window_GetRect(self.hwnd)if r then reaper.JS_Window_Resize(self.hwnd,x2-x1,y2-y1)self:_discover_os_window_frame_size(self.hwnd)end end if reaper.JS_Window_IsVisible(self.hwnd)then restyle()else rtk.defer(restyle)end local x,y,w,h=self:_get_geometry_from_attrs(overrides)local scaled_gfxw=gfx.w/rtk.scale.framebuffer local scaled_gfxh=gfx.h/rtk.scale.framebuffer if not resized then if w==scaled_gfxw and h==scaled_gfxh then resized=0 elseif w<=scaled_gfxw and h<=scaled_gfxh then resized=-1 elseif w>scaled_gfxw or h>scaled_gfxh then resized=1 end end local r,lastx,lasty,x2,y2=reaper.JS_Window_GetClientRect(self.hwnd)local moved=r and(self.x~=lastx or self.y~=lasty)local borderless_toggled=calc.borderless~=self._last_synced_attrs.borderless if moved or resized~=0 or borderless_toggled then local sw,sh=w,h if not calc.borderless then sw=w+self._os_window_frame_width sh=h+self._os_window_frame_height end sw=math.ceil(sw)sh=math.ceil(sh)reaper.JS_Window_SetPosition(self.hwnd,x,y,sw,sh)end if resized~=0 then gfx.w=w*rtk.scale.framebuffer gfx.h=h*rtk.scale.framebuffer self:queue_blit()self:onresize(scaled_gfxw,scaled_gfxh)end if moved then self:sync('x', x, 0)self:sync('y', y, 0)self:onmove(lastx,lasty)end reaper.JS_Window_SetOpacity(self.hwnd, 'ALPHA', calc.opacity)reaper.JS_Window_SetTitle(self.hwnd,calc.title)else local flags=reaper.JS_Window_GetLong(self.hwnd, 'EXSTYLE')flags=flags&~0x00080000 reaper.JS_Window_SetLong(self.hwnd, 'EXSTYLE', flags)end self._last_synced_attrs.borderless=calc.borderless return resized or 0 end function rtk.Window:open(options)if self.running or rtk._quit then return end local calc=self.calc rtk.window=self if options then options.halign=options.halign or options.align options.valign=options.valign or options.align end if not calc.borderless and self._os_window_frame_width==0 then self:_discover_os_window_frame_size(rtk.reaper_hwnd)end if not self.w or not self.h then self:reflow(rtk.Widget.REFLOW_FULL)end self.running=true gfx.ext_retina=1 self:_handle_attr('bg', calc.bg or rtk.theme.bg)options=self:_calc_cell_attrs(self,options)local x,y,w,h=self:_get_geometry_from_attrs(options)self:sync('x', x, 0)self:sync('y', y, 0)self:sync('w', w)self:sync('h', h)local dockstate=self:_get_dockstate_from_attrs()gfx.init(calc.title,calc.w/rtk.scale.framebuffer,calc.h/rtk.scale.framebuffer,dockstate,x,y)gfx.update()if gfx.ext_retina==2 and rtk.os.mac and rtk.scale.framebuffer~=2 then log.warning('rtk.Window:open(): unexpected adjustment to rtk.scale.framebuffer: %s -> 2', rtk.scale.framebuffer)rtk.scale.framebuffer=2 calc.w=calc.w*rtk.scale.framebuffer calc.h=calc.h*rtk.scale.framebuffer end dockstate,_,_=gfx.dock(-1,true,true)self:_handle_dock_change(dockstate)if rtk.has_js_reascript_api then self:_clear_gdi()else rtk.color.set(rtk.theme.bg)gfx.rect(0,0,w,h,1)end self._draw_queued=true if not self._run_queued then self:_run()end end function rtk.Window:_close()self.running=false gfx.quit()end function rtk.Window:close()local event=rtk.Event{type=rtk.Event.WINDOWCLOSE}self:_handle_window_event(event,reaper.time_precise())self.hwnd=nil self:_close()self:onclose()end function rtk.Window:_setup_borderless()if self._move_grip then return end local calc=self.calc local move=rtk.Spacer{z=-10000,w=1.0,h=30,touch_activate_delay=0}move.onmousedown=function(this,event)if not calc.docked and calc.borderless then local _,wx,wy,_,_=reaper.JS_Window_GetClientRect(self.hwnd)local mx,my=reaper.GetMousePosition()this._drag_start_mx=mx this._drag_start_my=my this._drag_start_wx=wx this._drag_start_wy=wy this._drag_start_ww=gfx.w/rtk.scale.framebuffer this._drag_start_wh=gfx.h/rtk.scale.framebuffer this._drag_start_dx=mx-wx this._drag_start_dy=my-wy end return true end move.ondragstart=function(this,event)if not calc.docked and calc.borderless and this._drag_start_mx then return true else return false end end move.ondragend=function(this,event)this._drag_start_mx=nil end move.ondragmousemove=function(this,event)local _,wx,wy,_,wy2=reaper.JS_Window_GetClientRect(self.hwnd)local mx,my=reaper.GetMousePosition()local x=mx-this._drag_start_dx local y if rtk.os.mac then local h=wy-wy2 y=my-this._drag_start_dy-h else y=my-this._drag_start_dy end if self._unmaximized_geometry then local _,_,w,h=table.unpack(self._unmaximized_geometry)local sx,_,sw,sh=self:_get_display_resolution()local xoffset=event.x/rtk.scale.framebuffer local dx=math.ceil(w*xoffset/this._drag_start_ww)x=rtk.clamp(sx+xoffset-dx,sx,sx+sw-w)self._unmaximized_geometry=nil this._drag_start_ww=w this._drag_start_wh=h this._drag_start_dx=dx if rtk.os.mac then y=(wy-h)+(my-this._drag_start_my)end reaper.JS_Window_SetPosition(self.hwnd,x,y,w,h)else reaper.JS_Window_Move(self.hwnd,x,y)end end move.ondoubleclick=function(this,event)if calc.docked or not calc.borderless then return end local x,y,w,h=self:_get_display_resolution(true)if self._unmaximized_geometry then if math.abs(w-self.w)>8)&0xff self:sync('dock', calc.dock)self:sync('docked', calc.docked)self._dockstate=dockstate self.hwnd=self:_get_hwnd()self:queue_reflow(rtk.Widget.REFLOW_FULL)if was_docked~=calc.docked then self:_clear_gdi()if calc.docked then self._undocked_geometry={self.x,self.y,self.w,self.h}elseif self._undocked_geometry then local x,y,w,h=table.unpack(self._undocked_geometry)local gw=w*rtk.scale.framebuffer local gh=h*rtk.scale.framebuffer self:sync('x', x, 0)self:sync('y', y, 0)self:sync('w', w, gw)self:sync('h', h, gh)gfx.w=gw gfx.h=gh end end self:_sync_window_attrs()self:queue_blit()self:ondock()end function rtk.Window:queue_reflow(mode,widget)if mode~=rtk.Widget.REFLOW_FULL and widget and widget.box then if self._reflow_widgets then self._reflow_widgets[widget]=true elseif not self._reflow_queued then self._reflow_widgets={[widget]=true}end else self._reflow_widgets=nil end self._reflow_queued=true end function rtk.Window:queue_draw()self._draw_queued=true end function rtk.Window:queue_blit()self._blits_queued=self._blits_queued+2 end function rtk.Window:queue_mouse_refresh()self._mouse_refresh_queued=true end function rtk.Window:_get_content_size(boxw,boxh,fillw,fillh,clampw,clamph,scale,greedyw,greedyh)local calc=self.calc local tp,rp,bp,lp=self:_get_padding_and_border()local w=rtk.isrel(self.w)and(self.w*boxw)or(self.w and(calc.w-lp-rp))or nil local h=rtk.isrel(self.h)and(self.h*boxh)or(self.h and(calc.h-tp-bp))or nil local minw,maxw,minh,maxh=self:_get_min_max_sizes(boxw,boxh,greedyw,greedyh,scale)return w,h,tp,rp,bp,lp,minw,maxw,minh,maxh end function rtk.Window:_get_min_max_sizes(boxw,boxh,greedyw,greedyh,scale)if not self._sync_window_attrs_on_update then return end local calc=self.calc local sx,sy,sw,sh=self:_get_display_resolution(true,not calc.borderless)scale=rtk.scale.framebuffer local minw,maxw,minh,maxh=rtk.Container._get_min_max_sizes(self,sw*scale,sh*scale,true,true,scale)return minw,maxw,minh,maxh,sx,sy,sw,sh end function rtk.Window:_reflow(boxx,boxy,boxw,boxh,fillw,filly,clampw,clamph,uiscale,viewport,window,greedyw,greedyh)local calc=self.calc rtk.Container._reflow(self,boxx,boxy,boxw,boxh,fillw,filly,clampw,clamph,uiscale,viewport,window,greedyw,greedyh)calc.x=0 calc.y=0 end function rtk.Window:reflow(mode)local calc=self.calc local widgets=self._reflow_widgets local full=false self._reflow_queued=false self._reflow_widgets=nil local t0=reaper.time_precise()if mode~=rtk.Widget.REFLOW_FULL and widgets and self.realized and #widgets<20 then for widget,_ in pairs(widgets)do widget:reflow()widget:_realize_geometry()end else if #self.children==0 then calc.w=self.w and calc.w or calc.minw calc.h=self.h and calc.h or calc.minh else local saved_size local boxw,boxh=calc.w,calc.h if not self.w or not self.h or rtk.isrel(self.w)or rtk.isrel(self.h)then local _,_,sw,sh=self:_get_display_resolution(true,not calc.borderless)boxw=(rtk.isrel(self.w)or not self.w)and sw*rtk.scale.framebuffer or boxw boxh=(rtk.isrel(self.h)or not self.h)and sh*rtk.scale.framebuffer or boxh end local _,_,w,h=rtk.Container.reflow(self,0,0,boxw,boxh,nil,nil,true,true,rtk.scale.value,nil,self,self.w~=nil,self.h~=nil )self:_realize_geometry()full=true end end local reflow_time=reaper.time_precise()-t0 if reflow_time>0.02 then log.warning("rtk: slow reflow: %s", reflow_time)end self:onreflow(widgets)self._draw_queued=true return full end function rtk.Window:_get_mouse_button_event(bit,type)if not type then if rtk.mouse.down&bit==0 and gfx.mouse_cap&bit~=0 then rtk.mouse.down=rtk.mouse.down|bit type=rtk.Event.MOUSEDOWN elseif rtk.mouse.down&bit~=0 and gfx.mouse_cap&bit==0 then rtk.mouse.down=rtk.mouse.down&~bit type=rtk.Event.MOUSEUP end end if type then local event=self._event:reset(type)event.x,event.y=gfx.mouse_x,gfx.mouse_y event:set_modifiers(gfx.mouse_cap,bit)return event end end function rtk.Window:_get_mousemove_event(simulated)local event=self._event:reset(rtk.Event.MOUSEMOVE)event.simulated=simulated event:set_modifiers(gfx.mouse_cap,rtk.mouse.state.latest or 0)return event end local function _get_wheel_distance(v)if rtk.os.mac then return-v/90 else return-v/120 end end function rtk.Window:_update()rtk.tick=rtk.tick+1 local calc=self.calc local now=reaper.time_precise()local need_draw=false if gfx.ext_retina~=rtk.scale.system then rtk.scale.system=gfx.ext_retina rtk.scale._calc()self:queue_reflow()end local files=nil local _,fname=gfx.getdropfile(0)if fname then files={fname}local idx=1 while true do _,fname=gfx.getdropfile(idx)if not fname then break end files[#files+1]=fname idx=idx+1 end gfx.getdropfile(-1)end gfx.update()if rtk._soon_funcs then rtk._run_soon()end local focus_changed=false if rtk.has_js_reascript_api then rtk.focused_hwnd=reaper.JS_Window_GetFocus()local is_focused=self.hwnd==rtk.focused_hwnd if is_focused~=self.is_focused then self.is_focused=is_focused need_draw=true focus_changed=true end end if self:onupdate()==false then return end need_draw=rtk._do_animations(now)or need_draw if self._sync_window_attrs_on_update then if self:_sync_window_attrs()~=0 then self:reflow(rtk.Widget.REFLOW_FULL)need_draw=true end self._sync_window_attrs_on_update=false end local dockstate,x,y=gfx.dock(-1,true,true)local dock_changed=dockstate~=self._dockstate if dock_changed then self:_handle_dock_change(dockstate)end if x~=self.x or y~=self.y then local lastx,lasty=self.x,self.y self:sync('x', x, 0)self:sync('y', y, 0)self:onmove(lastx,lasty)end local resized=gfx.w~=calc.w or gfx.h~=calc.h if resized and self.visible then local last_w,last_h=self.w,self.h self:sync('w', gfx.w / rtk.scale.framebuffer, gfx.w)self:sync('h', gfx.h / rtk.scale.framebuffer, gfx.h)self:_clear_gdi(calc.w,calc.h)self:onresize(last_w,last_h)self:reflow(rtk.Widget.REFLOW_FULL)need_draw=true elseif self._reflow_queued then self:reflow()need_draw=true end local event=nil calc.cursor=rtk.mouse.cursors.UNDEFINED if gfx.mouse_wheel~=0 or gfx.mouse_hwheel~=0 then event=self._event:reset(rtk.Event.MOUSEWHEEL)event:set_modifiers(gfx.mouse_cap,0)event.wheel=_get_wheel_distance(gfx.mouse_wheel)event.hwheel=_get_wheel_distance(gfx.mouse_hwheel)self:onmousewheel(event)gfx.mouse_wheel=0 gfx.mouse_hwheel=0 self:_handle_window_event(event,now)end local keycode=gfx.getchar()if keycode>0 then while keycode>0 do event=self._event:reset(rtk.Event.KEY)event:set_modifiers(gfx.mouse_cap,0)event:set_keycode(keycode)self:onkeypresspre(event)self:_handle_window_event(event,now)self:onkeypresspost(event)if not event.handled then if event.keycode==rtk.keycodes.F12 and log.level<=log.DEBUG then rtk.debug=not rtk.debug self:queue_draw()elseif event.keycode==rtk.keycodes.ESCAPE and not self.docked then self:close()end end keycode=gfx.getchar()end elseif keycode<0 then self:close()end if files then event=self:_get_mousemove_event(false)event.type=rtk.Event.DROPFILE event.files=files self:_handle_window_event(event,now)end rtk._touch_activate_event=rtk.touchscroll and rtk.Event.MOUSEUP or rtk.Event.MOUSEDOWN local mouse_button_changed=(rtk.mouse.down~=gfx.mouse_cap&rtk.mouse.BUTTON_MASK)local buttons_down=(gfx.mouse_cap&rtk.mouse.BUTTON_MASK~=0)local mouse_moved=(rtk.mouse.x~=gfx.mouse_x or rtk.mouse.y~=gfx.mouse_y)local last_in_window=self.in_window self.in_window=gfx.mouse_x>=0 and gfx.mouse_y>=0 and gfx.mouse_x<=gfx.w and gfx.mouse_y<=gfx.h local in_window_changed=self.in_window~=last_in_window need_draw=need_draw or self._draw_queued or in_window_changed if self._last_mousemove_time and rtk._mouseover_widget and rtk._mouseover_widget~=self._tooltip_widget and now-self._last_mousemove_time>rtk.tooltip_delay then self._tooltip_widget=rtk._mouseover_widget need_draw=true end if mouse_button_changed and rtk.touchscroll and self._jsx then self._restore_mouse_pos={self._jsx,self._jsy,nil}end if mouse_moved then if self.in_window then self._jsx=nil elseif not buttons_down then self._jsx,self._jsy=reaper.GetMousePosition()end if self._mouse_refresh_queued then self._mouse_refresh_queued=false local tmp=self:_get_mousemove_event(true)tmp.buttons=0 tmp.button=0 self:_handle_window_event(tmp,now)need_draw=true end end local suppress=false if not event or mouse_moved then if self.in_window and rtk.has_js_reascript_api and self.hwnd then local x,y=reaper.GetMousePosition()local hwnd=reaper.JS_Window_FromPoint(x,y)if hwnd~=self.hwnd then self.in_window=false in_window_changed=last_in_window~=false end end if need_draw or(mouse_moved and self.in_window)or in_window_changed or (rtk.dnd.dragging and buttons_down)then event=self:_get_mousemove_event(not mouse_moved)if buttons_down and rtk.touchscroll and not rtk.dnd.dragging then suppress=not event:get_button_state('mousedown-handled')end elseif rtk.mouse.down~=0 and not mouse_button_changed then local buttonstate=rtk.mouse.state[rtk.mouse.state.latest] local wait=math.max(rtk.long_press_delay,rtk.touch_activate_delay)if now-buttonstate.time<=wait+(2/rtk.fps)then event=self:_get_mouse_button_event(rtk.mouse.state.latest,rtk.Event.MOUSEDOWN)event.simulated=true end end if event and(not event.simulated or self._touch_scrolling.count==0 or buttons_down)then need_draw=need_draw or self._tooltip_widget~=nil self:_handle_window_event(event,now,suppress)end end rtk.mouse.x=gfx.mouse_x rtk.mouse.y=gfx.mouse_y if mouse_button_changed then event=self:_get_mouse_button_event(rtk.mouse.BUTTON_LEFT)if not event then event=self:_get_mouse_button_event(rtk.mouse.BUTTON_RIGHT)if not event then event=self:_get_mouse_button_event(rtk.mouse.BUTTON_MIDDLE)end end if event then if event.type==rtk.Event.MOUSEDOWN then local buttonstate=rtk.mouse.state[event.button] if not buttonstate then buttonstate={}rtk.mouse.state[event.button]=buttonstate end buttonstate.time=now buttonstate.tick=rtk.tick rtk.mouse.state.order[#rtk.mouse.state.order+1]=event.button rtk.mouse.state.latest=event.button elseif event.type==rtk.Event.MOUSEUP then if rtk.touchscroll and event.buttons==0 and self._restore_mouse_pos then self._restore_mouse_pos[3]=now+0.2 end end self:_handle_window_event(event,now)else log.warning('rtk: no event for mousecap=%s which indicates an internal rtk bug', gfx.mouse_cap)end end if rtk._soon_funcs then rtk._run_soon()end local blitted=false if event and calc.visible then if need_draw or self._draw_queued and not self._sync_window_attrs_on_update then if self._reflow_queued then if self:reflow()then calc.cursor=rtk.mouse.cursors.UNDEFINED local tmp=event:clone{type=rtk.Event.MOUSEMOVE,simulated=true}self:_handle_window_event(tmp,now)end end self._backingstore:resize(calc.w,calc.h,false)self._backingstore:pushdest()self:clear()self._draw_queued=false self:_draw(0,0,calc.alpha,event,calc.w,calc.h,0,0,0,0)if event.debug then event.debug:_draw_debug_info(event)end if self._tooltip_widget and not rtk.dnd.dragging then self._tooltip_widget:_draw_tooltip(rtk.mouse.x,rtk.mouse.y,calc.w,calc.h)end self._backingstore:popdest()self:_blit()blitted=true end if focus_changed then if self.is_focused then if self._focused_saved then self._focused_saved:focus(event)self._focused_saved=nil end self:onfocus(event)else if rtk.focused then self._focused_saved=rtk.focused rtk.focused:blur(event,nil)end self:onblur(event)end end if not event.handled and rtk.is_modal()and ((focus_changed and not self.is_focused)or event.type==rtk._touch_activate_event)then for _,info in pairs(rtk._modal)do local widget,modaltick=table.unpack(info)local state=rtk.mouse.state[event.button] if not state or(modaltick~=state.modaltick)then widget:_release_modal(event)end end end if not event.handled and rtk.focused and event.type==rtk._touch_activate_event then rtk.focused:blur(event,nil)end if event.type==rtk.Event.MOUSEUP then rtk.mouse.last[event.button]={x=event.x,y=event.y}for i=1,#rtk.mouse.state.order do if rtk.mouse.state.order[i]==event.button then table.remove(rtk.mouse.state.order,i)break end end if #rtk.mouse.state.order>0 then rtk.mouse.state.latest=rtk.mouse.state.order[#rtk.mouse.state.order] else rtk.mouse.state.latest=0 end rtk.mouse.state[event.button]=nil if event.buttons==0 then rtk._pressed_widgets=nil end end if calc.cursor==rtk.mouse.cursors.UNDEFINED then calc.cursor=self.cursor end if self.in_window and not suppress then if type(calc.cursor)=='userdata' then reaper.JS_Mouse_SetCursor(calc.cursor)reaper.JS_WindowMessage_Intercept(self.hwnd, "WM_SETCURSOR", false)else gfx.setcursor(calc.cursor,0)end elseif in_window_changed and self.hwnd and rtk.has_js_reascript_api then reaper.JS_WindowMessage_Release(self.hwnd, "WM_SETCURSOR")end end if self._restore_mouse_pos and not buttons_down then local x,y,when=table.unpack(self._restore_mouse_pos)if when and now>=when then reaper.JS_Mouse_SetPosition(x,y)self._restore_mouse_pos=nil end end if mouse_moved then self._last_mousemove_time=now end if self._blits_queued>0 then if not blitted then self:_blit()end self._blits_queued=self._blits_queued-1 end local duration=reaper.time_precise()-now if duration>0.04 then log.debug("rtk: very slow update: %s event=%s", duration, event)end end function rtk.Window:_blit()self._backingstore:blit{mode=rtk.Image.FAST_BLIT}end function rtk.Window:_handle_window_event(event,now,suppress)if not self.calc.visible then return end if not event.simulated then rtk._mouseover_widget=nil self._tooltip_widget=nil self._last_mousemove_time=nil end event.time=now if not suppress then self:_handle_event(0,0,event,false,rtk._modal==nil)end assert(event.type~=rtk.Event.MOUSEDOWN or event.button~=0)if event.type==rtk.Event.MOUSEUP then self._last_mouseup_time=event.time rtk._drag_candidates=nil if rtk.dnd.dropping then rtk.dnd.dropping:_handle_dropblur(event,rtk.dnd.dragging,rtk.dnd.arg)rtk.dnd.dropping=nil end if rtk.dnd.dragging and event.buttons&rtk.dnd.buttons==0 then rtk.dnd.dragging:_handle_dragend(event,rtk.dnd.arg)rtk.dnd.dragging=nil rtk.dnd.arg=nil local tmp=event:clone{type=rtk.Event.MOUSEMOVE,simulated=true}rtk.Container._handle_event(self,0,0,tmp,false,rtk._modal==nil)end elseif rtk._drag_candidates and event.type==rtk.Event.MOUSEMOVE and not event.simulated and event.buttons~=0 and not rtk.dnd.arg then event.handled=nil rtk.dnd.droppable=true local missed=false local dthresh=math.ceil(rtk.scale.value ^ 1.7)if rtk.touchscroll and event.time-self._last_mouseup_time<0.2 then dthresh=rtk.scale.value*10 end for n,state in ipairs(rtk._drag_candidates)do local widget,offered=table.unpack(state)if not offered then local ex,ey,when=table.unpack(rtk._pressed_widgets[widget.id])local dx=math.abs(ex-event.x)local dy=math.abs(ey-event.y)local tthresh=widget:_get_touch_activate_delay(event)if event.time-when>=tthresh and(dx>dthresh or dy>dthresh)then local arg,droppable=widget:_handle_dragstart(event,ex,ey,when)if arg then widget:_deferred_mousedown(event,ex,ey)rtk.dnd.dragging=widget rtk.dnd.arg=arg rtk.dnd.droppable=droppable~=false and true or false rtk.dnd.buttons=event.buttons widget:_handle_dragmousemove(event,arg)break elseif event.handled then break end state[2]=true else missed=true end end end if not missed or event.handled then rtk._drag_candidates=nil end end end function rtk.Window:request_mouse_cursor(cursor,force)if cursor and(self.calc.cursor==rtk.mouse.cursors.UNDEFINED or force)then self.calc.cursor=cursor return true else return false end end function rtk.Window:clear()self._backingstore:clear(self.calc.bg or rtk.theme.bg)end function rtk.Window:get_normalized_y()if not rtk.os.mac then return self.y else local _,_,_,sh=self:_get_display_resolution()return sh-self.y-gfx.h/rtk.scale.framebuffer-self._os_window_frame_height end end function rtk.Window:_set_touch_scrolling(viewport,state)local ts=self._touch_scrolling local exists=ts[viewport.id]~=nil if state and not exists then ts[viewport.id]=viewport ts.count=ts.count+1 elseif not state and exists then ts[viewport.id]=nil ts.count=ts.count-1 end end function rtk.Window:_is_touch_scrolling(viewport)if viewport then return self._touch_scrolling[viewport.id]~=nil else return self._touch_scrolling.count>0 end end function rtk.Window:onupdate()end function rtk.Window:onreflow(widgets)end function rtk.Window:onmove(lastx,lasty)end function rtk.Window:onresize(lastw,lasth)end function rtk.Window:ondock()end function rtk.Window:onclose()end function rtk.Window:onkeypresspre(event)end function rtk.Window:onkeypresspost(event)end end)() __mod_rtk_box=(function() local rtk=__mod_rtk_core local log=__mod_rtk_log rtk.Box=rtk.class('rtk.Box', rtk.Container)rtk.Box.static.HORIZONTAL=1 rtk.Box.static.VERTICAL=2 rtk.Box.static.FLEXSPACE={}rtk.Box.static.STRETCH_NONE=0 rtk.Box.static.STRETCH_FULL=1 rtk.Box.static.STRETCH_TO_SIBLINGS=2 rtk.Box.register{expand=rtk.Attribute{type='number'},fillw=false,fillh=false,stretch=rtk.Attribute{calculate={none=rtk.Box.STRETCH_NONE,full=rtk.Box.STRETCH_FULL,siblings=rtk.Box.STRETCH_TO_SIBLINGS,['true']=rtk.Box.STRETCH_FULL,['false']=rtk.Box.STRETCH_NONE,[true]=rtk.Box.STRETCH_FULL,[false]=rtk.Box.STRETCH_NONE,[rtk.Attribute.NIL]=rtk.Box.STRETCH_NONE,}},bg=nil,orientation=nil,spacing=rtk.Attribute{default=0,reflow=rtk.Widget.REFLOW_FULL,},}function rtk.Box:initialize(attrs,...)rtk.Container.initialize(self,attrs,self.class.attributes.defaults,...)assert(self.orientation, 'rtk.Box cannot be instantiated directly, use rtk.HBox or rtk.VBox instead')end function rtk.Box:_validate_child(child)if child~=rtk.Box.FLEXSPACE then rtk.Container._validate_child(self,child)end end function rtk.Box:_reflow(boxx,boxy,boxw,boxh,fillw,fillh,clampw,clamph,uiscale,viewport,window,greedyw,greedyh)local calc=self.calc calc.x,calc.y=self:_get_box_pos(boxx,boxy)local w,h,tp,rp,bp,lp,minw,maxw,minh,maxh=self:_get_content_size(boxw,boxh,fillw,fillh,clampw,clamph,nil,greedyw,greedyh )local inner_maxw=rtk.clamp(w or(boxw-lp-rp),minw,maxw)local inner_maxh=rtk.clamp(h or(boxh-tp-bp),minh,maxh)clampw=clampw or w~=nil or fillw clamph=clamph or h~=nil or fillh self._reflowed_children={}self._child_index_by_id={}local innerw,innerh,expw,exph,expand_units,remaining_size,total_spacing=self:_reflow_step1(inner_maxw,inner_maxh,clampw,clamph,uiscale,viewport,window,greedyw,greedyh )if self.orientation==rtk.Box.HORIZONTAL then expw=(expand_units>0)or expw elseif self.orientation==rtk.Box.VERTICAL then exph=(expand_units>0)or exph end innerw,innerh=self:_reflow_step2(inner_maxw,inner_maxh,innerw,innerh,clampw,clamph,expand_units,remaining_size,total_spacing,uiscale,viewport,window,greedyw,greedyh,tp,rp,bp,lp )fillw=fillw or(self.w and tonumber(self.w)<1.0)fillh=fillh or(self.h and tonumber(self.h)<1.0)innerw=w or math.max(innerw,fillw and greedyw and inner_maxw or 0)innerh=h or math.max(innerh,fillh and greedyh and inner_maxh or 0)calc.w=math.ceil(rtk.clamp(innerw+lp+rp,minw,maxw))calc.h=math.ceil(rtk.clamp(innerh+tp+bp,minh,maxh))return expw,exph end function rtk.Box:_reflow_step1(w,h,clampw,clamph,uiscale,viewport,window,greedyw,greedyh)local calc=self.calc local orientation=calc.orientation local remaining_size,greedy if orientation==rtk.Box.HORIZONTAL then remaining_size=w greedy=greedyw else remaining_size=h greedy=greedyh end local expand_units=0 local maxw,maxh=0,0 local spacing=0 local total_spacing=0 local expw,exph=false,false for n,widgetattrs in ipairs(self.children)do local widget,attrs=table.unpack(widgetattrs)local wcalc=widget.calc attrs._cellbox=nil if widget.id then self._child_index_by_id[widget.id]=n end if widget==rtk.Box.FLEXSPACE then expand_units=expand_units+(attrs.expand or 1)spacing=0 elseif widget.visible==true then attrs._halign=attrs.halign or calc.halign attrs._valign=attrs.valign or calc.valign attrs._minw=self:_adjscale(attrs.minw,uiscale,greedyw and w)attrs._maxw=self:_adjscale(attrs.maxw,uiscale,greedyw and w)attrs._minh=self:_adjscale(attrs.minh,uiscale,greedyh and h)attrs._maxh=self:_adjscale(attrs.maxh,uiscale,greedyh and h)local implicit_expand if orientation==rtk.Box.HORIZONTAL then implicit_expand=attrs.fillw and greedyw else implicit_expand=attrs.fillh and greedyh end attrs._calculated_expand=attrs.expand or(implicit_expand and 1)or 0 if attrs._calculated_expand==0 and implicit_expand then log.error('rtk.Box: %s: fill=true overrides explicit expand=0: %s will be expanded', self, widget)end if attrs._calculated_expand==0 or not greedy then local ww,wh=0,0 local wexpw,wexph local ctp,crp,cbp,clp=self:_get_cell_padding(widget,attrs)if orientation==rtk.Box.HORIZONTAL then local child_maxw=rtk.clamp(remaining_size-clp-crp-spacing,attrs._minw,attrs._maxw)local child_maxh=rtk.clamp(h-ctp-cbp,attrs._minh,attrs._maxh)_,_,ww,wh,wexpw,wexph=widget:reflow(0,0,child_maxw,child_maxh,attrs.fillw and greedyw,attrs.fillh and greedyh and attrs.stretch~=rtk.Box.STRETCH_TO_SIBLINGS,clampw,clamph,uiscale,viewport,window,greedyw,greedyh )expw=wexpw or expw exph=wexph or exph ww=math.max(ww,attrs._minw or 0)wh=math.max(wh,attrs._minh or 0)if wexpw and clampw and ww>=child_maxw and n<#self.children then attrs._calculated_expand=1 end else local child_maxw=rtk.clamp(w-clp-crp,attrs._minw,attrs._maxw)local child_maxh=rtk.clamp(remaining_size-ctp-cbp-spacing,attrs._minh,attrs._maxh)_,_,ww,wh,wexpw,wexph=widget:reflow(0,0,child_maxw,child_maxh,attrs.fillw and greedyw and attrs.stretch~=rtk.Box.STRETCH_TO_SIBLINGS,attrs.fillh and greedyh,clampw,clamph,uiscale,viewport,window,greedyw,greedyh )expw=wexpw or expw exph=wexph or exph ww=math.max(ww,attrs._minw or 0)wh=math.max(wh,attrs._minh or 0)if wexph and clamph and wh>=child_maxh and n<#self.children then attrs._calculated_expand=1 end end expw=expw or(attrs.fillw and greedyw)exph=exph or(attrs.fillh and greedyh)if attrs._calculated_expand==0 and wcalc.position&rtk.Widget.POSITION_INFLOW~=0 then maxw=math.max(maxw,ww+clp+crp)maxh=math.max(maxh,wh+ctp+cbp)if orientation==rtk.Box.HORIZONTAL then remaining_size=remaining_size-(clampw and(ww+clp+crp+spacing)or 0)else remaining_size=remaining_size-(clamph and(wh+ctp+cbp+spacing)or 0)end else expand_units=expand_units+attrs._calculated_expand end else expand_units=expand_units+attrs._calculated_expand end if orientation==rtk.Box.VERTICAL and attrs.stretch==rtk.Box.STRETCH_FULL and greedyw then maxw=w elseif orientation==rtk.Box.HORIZONTAL and attrs.stretch==rtk.Box.STRETCH_FULL and greedyh then maxh=h end attrs._running_spacing_total=spacing spacing=(attrs.spacing or self.spacing)*rtk.scale.value total_spacing=total_spacing+spacing self:_add_reflowed_child(widgetattrs,attrs.z or wcalc.z or 0)else widget.realized=false end end self:_determine_zorders()return maxw,maxh,expw,exph,expand_units,remaining_size,total_spacing end end)() __mod_rtk_vbox=(function() local rtk=__mod_rtk_core rtk.VBox=rtk.class('rtk.VBox', rtk.Box)rtk.VBox.register{orientation=rtk.Box.VERTICAL }function rtk.VBox:initialize(attrs,...)rtk.Box.initialize(self,attrs,self.class.attributes.defaults,...)end function rtk.VBox:_reflow_step2(w,h,maxw,maxh,clampw,clamph,expand_units,remaining_size,total_spacing,uiscale,viewport,window,greedyw,greedyh,tp,rp,bp,lp)local expand_unit_size=expand_units>0 and((remaining_size-total_spacing)/expand_units)or 0 local offset=0 local spacing=0 local second_pass={}for n,widgetattrs in ipairs(self.children)do local widget,attrs=table.unpack(widgetattrs)local wcalc=widget.calc if widget==rtk.Box.FLEXSPACE then if greedyh then local previous=offset offset=offset+expand_unit_size*(attrs.expand or 1)spacing=0 maxh=math.max(maxh,offset)self:_set_cell_box(attrs,lp,tp+previous,maxw,offset-previous)end elseif widget.visible==true then local wx,wy,ww,wh local ctp,crp,cbp,clp=self:_get_cell_padding(widget,attrs)local need_second_pass=(attrs.stretch==rtk.Box.STRETCH_TO_SIBLINGS or (attrs._halign and attrs._halign~=rtk.Widget.LEFT and not(attrs.fillw and greedyw)and attrs.stretch~=rtk.Box.STRETCH_FULL))local offx=lp+clp local offy=offset+tp+ctp+spacing local expand=attrs._calculated_expand local cellh if expand and greedyh and expand>0 then local expanded_size=(expand_unit_size*expand)expand_units=expand_units-expand if attrs._minh and attrs._minh>expanded_size then local remaining_spacing=total_spacing-attrs._running_spacing_total expand_unit_size=(remaining_size-attrs._minh-ctp-cbp-remaining_spacing)/expand_units end local child_maxw=rtk.clamp(w-clp-crp,attrs._minw,attrs._maxw)local child_maxh=rtk.clamp(expanded_size-ctp-cbp-spacing,attrs._minh,attrs._maxh)child_maxh=math.min(child_maxh,h-maxh-spacing)wx,wy,ww,wh=widget:reflow(0,0,child_maxw,child_maxh,attrs.fillw,attrs.fillh,clampw,clamph,uiscale,viewport,window,greedyw,greedyh )if attrs.stretch==rtk.Box.STRETCH_FULL and greedyw then ww=maxw end wh=math.max(child_maxh,wh)cellh=ctp+wh+cbp remaining_size=remaining_size-spacing-cellh if need_second_pass then second_pass[#second_pass+1]={widget,attrs,offx,offy,ww,child_maxh,ctp,crp,cbp,clp,offset,spacing }else self:_align_child(widget,attrs,offx,offy,ww,child_maxh,crp,cbp)self:_set_cell_box(attrs,lp,tp+offset+spacing,ww+clp+crp,cellh)end else ww=attrs.stretch==rtk.Box.STRETCH_FULL and greedyw and(maxw-clp-crp)or wcalc.w wh=math.max(wcalc.h,attrs._minh or 0)cellh=ctp+wh+cbp if need_second_pass then second_pass[#second_pass+1]={widget,attrs,offx,offy,ww,wh,ctp,crp,cbp,clp,offset,spacing }else self:_align_child(widget,attrs,offx,offy,ww,wh,crp,cbp)self:_set_cell_box(attrs,lp,tp+offset+spacing,ww+clp+crp,cellh)end end if wcalc.position&rtk.Widget.POSITION_INFLOW~=0 then offset=offset+spacing+cellh end maxw=math.max(maxw,ww+clp+crp)maxh=math.max(maxh,offset)spacing=(attrs.spacing or self.spacing)*uiscale if not need_second_pass then widget:_realize_geometry()end end end if #second_pass>0 then for n,widgetinfo in ipairs(second_pass)do local widget,attrs,offx,offy,ww,child_maxh,ctp,crp,cbp,clp,offset,spacing=table.unpack(widgetinfo)if attrs.stretch==rtk.Box.STRETCH_TO_SIBLINGS then widget:reflow(0,0,maxw,child_maxh,attrs.fillw,attrs.fillh,clampw,clamph,uiscale,viewport,window,greedyw,greedyh )end self:_align_child(widget,attrs,offx,offy,maxw,child_maxh,crp,cbp)self:_set_cell_box(attrs,lp,tp+offset+spacing,maxw+clp+crp,child_maxh+ctp+cbp)widget:_realize_geometry()end end return maxw,maxh end function rtk.VBox:_align_child(widget,attrs,offx,offy,cellw,cellh,crp,cbp)local x,y=offx,offy local wcalc=widget.calc if cellh>wcalc.h then if attrs._valign==rtk.Widget.BOTTOM then y=(offy-cbp)+cellh-wcalc.h-cbp elseif attrs._valign==rtk.Widget.CENTER then y=offy+(cellh-wcalc.h)/2 end end if attrs._halign==rtk.Widget.CENTER then x=(offx-crp)+(cellw-wcalc.w)/2 elseif attrs._halign==rtk.Widget.RIGHT then x=offx+cellw-wcalc.w-crp end wcalc.x=wcalc.x+x widget.box[1]=x wcalc.y=wcalc.y+y widget.box[2]=y end end)() __mod_rtk_hbox=(function() local rtk=__mod_rtk_core rtk.HBox=rtk.class('rtk.HBox', rtk.Box)rtk.HBox.register{orientation=rtk.Box.HORIZONTAL }function rtk.HBox:initialize(attrs,...)rtk.Box.initialize(self,attrs,self.class.attributes.defaults,...)end function rtk.HBox:_reflow_step2(w,h,maxw,maxh,clampw,clamph,expand_units,remaining_size,total_spacing,uiscale,viewport,window,greedyw,greedyh,tp,rp,bp,lp)local expand_unit_size=expand_units>0 and((remaining_size-total_spacing)/expand_units)or 0 local offset=0 local spacing=0 local second_pass={}for n,widgetattrs in ipairs(self.children)do local widget,attrs=table.unpack(widgetattrs)local wcalc=widget.calc if widget==rtk.Box.FLEXSPACE then if greedyw then local previous=offset offset=offset+expand_unit_size*(attrs.expand or 1)spacing=0 maxw=math.max(maxw,offset)self:_set_cell_box(attrs,lp+previous,tp,offset-previous,maxh)end elseif widget.visible==true then local wx,wy,ww,wh local ctp,crp,cbp,clp=self:_get_cell_padding(widget,attrs)local need_second_pass=(attrs.stretch==rtk.Box.STRETCH_TO_SIBLINGS or (attrs._valign and attrs._valign~=rtk.Widget.TOP and not(attrs.fillh and greedyh)and attrs.stretch~=rtk.Box.STRETCH_FULL))local offx=offset+lp+clp+spacing local offy=tp+ctp local expand=attrs._calculated_expand local cellw if expand and greedyw and expand>0 then local expanded_size=(expand_unit_size*expand)expand_units=expand_units-expand if attrs._minw and attrs._minw>expanded_size then local remaining_spacing=total_spacing-attrs._running_spacing_total expand_unit_size=(remaining_size-attrs._minw-clp-crp-remaining_spacing)/expand_units end local child_maxw=rtk.clamp(expanded_size-clp-crp,attrs._minw,attrs._maxw)child_maxw=math.min(child_maxw,w-maxw-spacing)local child_maxh=rtk.clamp(h-ctp-cbp,attrs._minh,attrs._maxh)wx,wy,ww,wh=widget:reflow(0,0,child_maxw,child_maxh,attrs.fillw,attrs.fillh,clampw,clamph,uiscale,viewport,window,greedyw,greedyh )if attrs.stretch==rtk.Box.STRETCH_FULL and greedyh then wh=maxh end ww=math.max(child_maxw,ww)cellw=clp+ww+crp remaining_size=remaining_size-spacing-cellw if need_second_pass then second_pass[#second_pass+1]={widget,attrs,offx,offy,child_maxw,wh,ctp,crp,cbp,clp,offset,spacing }else self:_align_child(widget,attrs,offx,offy,child_maxw,wh,crp,cbp)self:_set_cell_box(attrs,lp+offset+spacing,tp,cellw,wh+ctp+cbp)end else ww=math.max(wcalc.w,attrs._minw or 0)wh=attrs.stretch==rtk.Box.STRETCH_FULL and greedyh and(maxh-ctp-cbp)or wcalc.h cellw=clp+ww+crp if need_second_pass then second_pass[#second_pass+1]={widget,attrs,offx,offy,ww,wh,ctp,crp,cbp,clp,offset,spacing }else self:_align_child(widget,attrs,offx,offy,ww,wh,crp,cbp)self:_set_cell_box(attrs,lp+offset+spacing,tp,cellw,wh+ctp+cbp)end end if wcalc.position&rtk.Widget.POSITION_INFLOW~=0 then offset=offset+spacing+cellw end maxw=math.max(maxw,offset)maxh=math.max(maxh,wh+ctp+cbp)spacing=(attrs.spacing or self.spacing)*uiscale if not need_second_pass then widget:_realize_geometry()end end end if #second_pass>0 then for n,widgetinfo in ipairs(second_pass)do local widget,attrs,offx,offy,child_maxw,wh,ctp,crp,cbp,clp,offset,spacing=table.unpack(widgetinfo)if attrs.stretch==rtk.Box.STRETCH_TO_SIBLINGS then widget:reflow(0,0,child_maxw,maxh,attrs.fillw,attrs.fillh,clampw,clamph,uiscale,viewport,window,greedyw,greedyh )end self:_align_child(widget,attrs,offx,offy,child_maxw,maxh,crp,cbp)self:_set_cell_box(attrs,lp+offset+spacing,tp,child_maxw+clp+crp,maxh+ctp+cbp)widget:_realize_geometry()end end return maxw,maxh end function rtk.HBox:_align_child(widget,attrs,offx,offy,cellw,cellh,crp,cbp)local x,y=offx,offy local wcalc=widget.calc if cellw>wcalc.w then if attrs._halign==rtk.Widget.RIGHT then x=(offx-crp)+cellw-wcalc.w-crp elseif attrs._halign==rtk.Widget.CENTER then x=offx+(cellw-wcalc.w)/2 end end if attrs._valign==rtk.Widget.CENTER then y=(offy-cbp)+(cellh-wcalc.h)/2 elseif attrs._valign==rtk.Widget.BOTTOM then y=offy+cellh-wcalc.h-cbp end wcalc.x=wcalc.x+x widget.box[1]=x wcalc.y=wcalc.y+y widget.box[2]=y end end)() __mod_rtk_flowbox=(function() local rtk=__mod_rtk_core rtk.FlowBox=rtk.class('rtk.FlowBox', rtk.Container)rtk.FlowBox.register{vspacing=rtk.Attribute{default=0,reflow=rtk.Widget.REFLOW_FULL,},hspacing=rtk.Attribute{default=0,reflow=rtk.Widget.REFLOW_FULL,},}function rtk.FlowBox:initialize(attrs,...)rtk.Container.initialize(self,attrs,self.class.attributes.defaults,...)end function rtk.FlowBox:_reflow(boxx,boxy,boxw,boxh,fillw,fillh,clampw,clamph,uiscale,viewport,window,greedyw,greedyh)local calc=self.calc local x,y=self:_get_box_pos(boxx,boxy)local w,h,tp,rp,bp,lp,minw,maxw,minh,maxh=self:_get_content_size(boxw,boxh,fillw,fillh,clampw,clamph,nil,greedyw,greedyh )local inner_maxw=rtk.clamp(w or(boxw-lp-rp),minw,maxw)local inner_maxh=rtk.clamp(h or(boxh-tp-bp),minh,maxh)clampw=clampw or w~=nil or fillw clamph=clamph or h~=nil or fillh local child_geometry={}local hspacing=(calc.hspacing or 0)*rtk.scale.value local vspacing=(calc.vspacing or 0)*rtk.scale.value self._reflowed_children={}self._child_index_by_id={}local child_maxw=0 local child_totalh=0 for _,widgetattrs in ipairs(self.children)do local widget,attrs=table.unpack(widgetattrs)local wcalc=widget.calc if wcalc.visible==true and wcalc.position&rtk.Widget.POSITION_INFLOW~=0 then local ctp,crp,cbp,clp=self:_get_cell_padding(widget,attrs)attrs._minw=self:_adjscale(attrs.minw,uiscale,greedyw and inner_maxw)attrs._maxw=self:_adjscale(attrs.maxw,uiscale,greedyw and inner_maxw)attrs._minh=self:_adjscale(attrs.minh,uiscale,greedyh and inner_maxh)attrs._maxh=self:_adjscale(attrs.maxh,uiscale,greedyh and inner_maxh)local wx,wy,ww,wh=widget:reflow(0,0,rtk.clamp(inner_maxw,attrs._minw,attrs._maxw),rtk.clamp(inner_maxh,attrs._minh,attrs._maxh),nil,nil,clampw,clamph,uiscale,viewport,window,false,false )ww=ww+clp+crp wh=wh+ctp+cbp child_maxw=math.min(math.max(child_maxw,ww,attrs._minw or 0),inner_maxw)child_totalh=child_totalh+math.max(wh,attrs._minh or 0)child_geometry[#child_geometry+1]={x=wx,y=wy,w=ww,h=wh}end end child_totalh=child_totalh+(#self.children-1)*vspacing local col_width=math.ceil(child_maxw)local num_columns=math.max(1,math.floor((inner_maxw+hspacing)/(col_width+hspacing)))local col_height=h if not col_height and #child_geometry>0 then col_height=child_geometry[1].h for i=2,#child_geometry do local need_columns=1 local cur_colh=0 for j=1,#child_geometry do local wh=child_geometry[j].h if cur_colh+wh>col_height then need_columns=need_columns+1 cur_colh=0 end cur_colh=cur_colh+wh+(j>1 and vspacing or 0)end if need_columns<=num_columns then num_columns=need_columns break end col_height=col_height+vspacing+child_geometry[i].h end end local col_width_max=math.floor((inner_maxw-((num_columns-1)*hspacing))/num_columns)local col={w=0,h=0,n=1}local offset={x=0,y=0}local inner={w=0,h=0}local chspacing=(col.ncol_height then inner.w=inner.w+col.w offset.x=offset.x+col.w offset.y=0 col.w,col.h=0,0 col.n=col.n+1 chspacing=(col.ncliph or self.calc.ghost then return false end self:_handle_drawpre(offx,offy,alpha,event)self:_draw_bg(offx,offy,alpha,event)self:_draw_borders(offx,offy,alpha)self:_handle_draw(offx,offy,alpha,event)end end)() __mod_rtk_button=(function() local rtk=__mod_rtk_core rtk.Button=rtk.class('rtk.Button', rtk.Widget)rtk.Button.static.RAISED=false rtk.Button.static.FLAT=true rtk.Button.static.LABEL=2 rtk.Button.register{[1]=rtk.Attribute{alias='label'},label=rtk.Attribute{reflow=rtk.Widget.REFLOW_FULL},icon=rtk.Attribute{priority=true,reflow=rtk.Widget.REFLOW_FULL,calculate=function(self,attr,value,target)if type(value)=='string' then local color=self.color if self.calc.flat==rtk.Button.FLAT then color=self.parent and self.parent.calc.bg or rtk.theme.bg end local style=rtk.color.get_icon_style(color,rtk.theme.bg)if self.icon and self.icon.style==style then return self.icon end local img=rtk.Image.icon(value,style)if not img then img=rtk.Image.make_placeholder_icon(24,24,style)end return img else return value end end,},wrap=rtk.Attribute{default=false,reflow=rtk.Widget.REFLOW_FULL,},color=rtk.Attribute{default=function(self,attr)return rtk.theme.button end,calculate=function(self,attr,value,target)local color=rtk.Widget.attributes.bg.calculate(self,attr,value,target)local luma=rtk.color.luma(color,rtk.theme.bg)local dark=luma0 and tagw or(calc.w-ix+calc.spacing))end if rtk.os.mac and icon then ly=ly+math.ceil(rtk.scale.value)end cliph=calc.h-ly end self._pre={tp=tp,rp=rp,bp=bp,lp=lp,ix=ix,iy=iy,lx=lx,ly=ly,lw=lw,lh=lh,tagx=tagx,tagw=tagw,surx=surx,sury=sury,surw=surw or 0,surh=surh or 0,clipw=clipw,cliph=cliph,iw=icon and(icon.w*iscale),ih=icon and(icon.h*iscale),}end function rtk.Button:_draw(offx,offy,alpha,event,clipw,cliph,cltargetx,cltargety,parentx,parenty)local calc=self.calc if calc.disabled then alpha=alpha*0.5 end rtk.Widget._draw(self,offx,offy,alpha,event,clipw,cliph,cltargetx,cltargety,parentx,parenty)local x=calc.x+offx local y=calc.y+offy if y+calc.h<0 or y>cliph or calc.ghost then return false end local hover=(self.hovering or calc.hover)and not calc.disabled local clicked=hover and event.buttons~=0 and self:focused()and self.window.is_focused local theme=self._theme local gradient,brightness,cmul,bmul if clicked then gradient=theme.button_clicked_gradient*theme.button_gradient_mul brightness=theme.button_clicked_brightness cmul=theme.button_clicked_mul bmul=theme.button_clicked_border_mul elseif hover then gradient=theme.button_hover_gradient*theme.button_gradient_mul brightness=theme.button_hover_brightness cmul=theme.button_hover_mul bmul=theme.button_hover_border_mul else gradient=theme.button_normal_gradient*theme.button_gradient_mul bmul=theme.button_normal_border_mul brightness=1.0 cmul=1.0 end self:_handle_drawpre(offx,offy,alpha,event)if self.circular then self:_draw_circular_button(x,y,hover,clicked,gradient,brightness,cmul,bmul,alpha)else self:_draw_rectangular_button(x,y,hover,clicked,gradient,brightness,cmul,bmul,alpha)self:_draw_borders(offx,offy,alpha)end self:_handle_draw(offx,offy,alpha,event)end function rtk.Button:_is_mouse_over(clparentx,clparenty,event)local calc=self.calc if calc.circular then local x=calc.x+clparentx+self._radius local y=calc.y+clparenty+self._radius return self.window and self.window.in_window and rtk.point_in_circle(event.x,event.y,x,y,self._radius)else return rtk.Widget._is_mouse_over(self,clparentx,clparenty,event)end end function rtk.Button:_draw_circular_button(x,y,hover,clicked,gradient,brightness,cmul,bmul,alpha)local calc=self.calc local radius=math.ceil(self._radius)local cirx=math.floor(x)+radius local ciry=math.floor(y)+radius local icon=calc.icon if calc.surface and(not calc.flat or hover or clicked)then if calc.elevation>0 then self._shadow:draw(x+1,y+1)end local r,g,b,a=rtk.color.mod(calc.color,1.0,1.0,brightness)self:setcolor({r*cmul,g*cmul,b*cmul,a},alpha)gfx.circle(cirx,ciry,radius,1,1)end if icon then local ix=(calc.w-(icon.w*rtk.scale.value))/2 local iy=(calc.h-(icon.h*rtk.scale.value))/2 self:_draw_icon(x+ix,y+iy,hover,alpha)end if calc.border then local color,thickness=table.unpack(calc.border)self:setcolor(color)for i=1,thickness do gfx.circle(cirx,ciry,radius-(i-1),0,1)end end end function rtk.Button:_draw_rectangular_button(x,y,hover,clicked,gradient,brightness,cmul,bmul,alpha)local calc=self.calc local pre=self._pre local amul=calc.alpha*alpha local label_over_surface=calc.surface and(calc.flat==rtk.Button.RAISED or hover)local textcolor=label_over_surface and calc.textcolor or calc.textcolor2 local draw_surface=label_over_surface or(calc.label and calc.tagged and calc.surface)local tagx=x+pre.tagx local surx=x+pre.surx local sury=y+pre.sury local surw=pre.surw local surh=pre.surh if calc.tagged and calc.flat==rtk.Button.LABEL and calc.surface and not hover then surx=tagx surw=pre.tagw end if surw>0 and surh>0 and draw_surface then local d=(gradient*calc.gradient)/calc.h local lmul=1-calc.h*d/2 local r,g,b,a=rtk.color.rgba(calc.color)local sr,sg,sb,sa=rtk.color.mod({r,g,b,a},1.0,1.0,brightness*lmul,amul)gfx.gradrect(surx,sury,surw,surh,sr*cmul,sg*cmul,sb*cmul,sa*amul,0,0,0,0,r*d,g*d,b*d,0)gfx.set(r*bmul,g*bmul,b*bmul,amul)gfx.rect(surx,sury,surw,surh,0)if pre.tagw>0 and(hover or calc.flat~=rtk.Button.LABEL)then local ta=1-(calc.tagalpha or self._theme.button_tag_alpha)self:setcolor({0,0,0,1})gfx.muladdrect(tagx,sury,pre.tagw,surh,ta,ta,ta,1.0)end elseif calc.bg then self:setcolor(calc.bg)gfx.rect(x,y,calc.w,calc.h,1)end if calc.icon then self:_draw_icon(x+pre.ix,y+pre.iy,hover,alpha)end if calc.label then self:setcolor(textcolor,alpha)self._font:draw(self._segments,x+pre.lx,y+pre.ly,pre.clipw,pre.cliph)end end function rtk.Button:_draw_icon(x,y,hovering,alpha)self.calc.icon:draw(x,y,self.calc.alpha*alpha,rtk.scale.value)end end)() __mod_rtk_entry=(function() local rtk=__mod_rtk_core local log=__mod_rtk_log rtk.Entry=rtk.class('rtk.Entry', rtk.Widget)rtk.Entry.static.contextmenu={{'Undo', id='undo'},rtk.NativeMenu.SEPARATOR,{'Cut', id='cut'},{'Copy', id='copy'},{'Paste', id='paste'},{'Delete', id='delete'},rtk.NativeMenu.SEPARATOR,{'Select All', id='select_all'},}rtk.Entry.register{[1]=rtk.Attribute{alias='value'},value=rtk.Attribute{default='',reflow=rtk.Widget.REFLOW_NONE,calculate=function(self,attr,value,target)return value and tostring(value) or ''end,},textwidth=rtk.Attribute{reflow=rtk.Widget.REFLOW_FULL},icon=rtk.Attribute{reflow=rtk.Widget.REFLOW_FULL,calculate=function(self,attr,value,target)if type(value)=='string' then local icon=self.calc.icon local parentbg=self.parent and self.parent.calc.bg local style=rtk.color.get_icon_style(self.calc.bg,parentbg or rtk.theme.bg)if icon and icon.style==style then return icon end local img=rtk.Image.icon(value,style)if not img then img=rtk.Image.make_placeholder_icon(24,24,style)end return img else return value end end },icon_alpha=0.6,spacing=rtk.Attribute{default=5,reflow=rtk.Widget.REFLOW_FULL },placeholder=rtk.Attribute{default=nil,reflow=rtk.Widget.REFLOW_FULL,},textcolor=rtk.Attribute{default=function(self,attr)return rtk.theme.text end,calculate=rtk.Reference('bg')},border_hover=rtk.Attribute{default=function(self,attr)return {rtk.theme.entry_border_hover,1}end,reflow=rtk.Widget.REFLOW_FULL,calculate=function(self,attr,value,target)return rtk.Widget.static._calc_border(self,value)end,},border_focused=rtk.Attribute{default=function(self,attr)return {rtk.theme.entry_border_focused,1}end,reflow=rtk.Widget.REFLOW_FULL,calculate=rtk.Reference('border_hover'),},blink=true,caret=rtk.Attribute{type='number',default=1,priority=true,reflow=rtk.Widget.REFLOW_NONE,calculate=function(self,attr,value,target)return rtk.clamp(value, 1, #(target.value or '') + 1)end,},font=rtk.Attribute{reflow=rtk.Widget.REFLOW_FULL,default=function(self,attr)return self._theme_font[1] end },fontsize=rtk.Attribute{reflow=rtk.Widget.REFLOW_FULL,default=function(self,attr)return self._theme_font[2] end },fontscale=rtk.Attribute{default=1.0,reflow=rtk.Widget.REFLOW_FULL },fontflags=rtk.Attribute{default=function(self,attr)return self._theme_font[3] end },bg=rtk.Attribute{default=function(self,attr)return rtk.theme.entry_bg end },tpadding=4,rpadding=10,bpadding=4,lpadding=10,cursor=rtk.mouse.cursors.BEAM,autofocus=true,}function rtk.Entry:initialize(attrs,...)self._theme_font=rtk.theme.entry_font or rtk.theme.default_font rtk.Widget.initialize(self,attrs,self.class.attributes.defaults,...)self._positions={0}self._backingstore=nil self._font=rtk.Font()self._caretctr=0 self._selstart=nil self._selend=nil self._loffset=0 self._blinking=false self._dirty_text=false self._dirty_positions=nil self._dirty_view=false self._history=nil self._last_doubleclick_time=0 self._num_doubleclicks=0 end function rtk.Entry:_handle_attr(attr,value,oldval,trigger,reflow,sync)local calc=self.calc local ok=rtk.Widget._handle_attr(self,attr,value,oldval,trigger,reflow,sync)if ok==false then return ok end if attr=='value' then self._dirty_text=true if not self._dirty_positions then local diff=math.min(#value,#oldval)for i=1,diff do if value:sub(i,i)~=oldval:sub(i,i)then diff=i break end end self._dirty_positions=diff end self._selstart=nil local caret=rtk.clamp(calc.caret,1,#value+1)if caret~=calc.caret then self:sync('caret', caret)end if trigger then self:_handle_change()end elseif attr=='caret' then self._dirty_view=true elseif attr == 'bg' and type(self.icon) == 'string' then self:attr('icon', self.icon, true)elseif attr=='icon' and value then self._last_reflow_scale=nil end return true end function rtk.Entry:_reflow(boxx,boxy,boxw,boxh,fillw,fillh,clampw,clamph,uiscale,viewport,window,greedyw,greedyh)local calc=self.calc local lmaxw,lmaxh=nil,nil if self._font:set(calc.font,calc.fontsize,calc.fontscale,calc.fontflags)then self._dirty_positions=1 end if calc.icon and uiscale~=self._last_reflow_scale then calc.icon:refresh_scale()self._last_reflow_scale=uiscale end if calc.textwidth and not self.w then local charwidth, _=gfx.measurestr('W')lmaxw,lmaxh=charwidth*calc.textwidth,self._font.texth else lmaxw, lmaxh=gfx.measurestr(calc.placeholder or "Dummy string!")end calc.x,calc.y=self:_get_box_pos(boxx,boxy)local w,h,tp,rp,bp,lp,minw,maxw,minh,maxh=self:_get_content_size(boxw,boxh,fillw,fillh,clampw,clamph,nil,greedyw,greedyh )calc.w=math.ceil(rtk.clamp((w or lmaxw)+lp+rp,minw,maxw))calc.h=math.ceil(rtk.clamp((h or lmaxh)+tp+bp,minh,maxh))self._ctp,self._crp,self._cbp,self._clp=tp,rp,bp,lp if not self._backingstore then self._backingstore=rtk.Image()end self._backingstore:resize(calc.w,calc.h,false)self._dirty_text=true end function rtk.Entry:_unrealize()rtk.Widget._unrealize(self)self._backingstore=nil end function rtk.Entry:_calcpositions(startfrom)startfrom=startfrom or 1 local value=self.calc.value self._font:set()for i=startfrom,#value+1 do local w,_=gfx.measurestr(value:sub(1,i))self._positions[i+1]=w end self._dirty_positions=nil end function rtk.Entry:_calcview()local calc=self.calc local curx=self._positions[calc.caret] local curoffset=curx-self._loffset local innerw=math.max(0,calc.w-(self._clp+self._crp))if calc.icon then innerw=innerw-(calc.icon.w*rtk.scale.value/calc.icon.density)-calc.spacing end local loffset=self._loffset if curoffset<0 then loffset=curx elseif curoffset>innerw then loffset=curx-innerw end local last=self._positions[#calc.value+1] if last>innerw then local gap=innerw-(last-loffset)if gap>0 then loffset=loffset-gap end else loffset=0 end if loffset~=self._loffset then self._dirty_text=true self._loffset=loffset end self._dirty_view=false end function rtk.Entry:_handle_focus(event,context)local ok=rtk.Widget._handle_focus(self,event,context)self._dirty_text=self._dirty_text or(ok and self._selstart)return ok end function rtk.Entry:_handle_blur(event,other)local ok=rtk.Widget._handle_blur(self,event,other)self._dirty_text=self._dirty_text or(ok and self._selstart)return ok end function rtk.Entry:_blink()if self.calc.blink and self:focused()then self._blinking=true local ctr=self._caretctr%16 self._caretctr=self._caretctr+1 if ctr==0 then self:queue_draw()end rtk.defer(self._blink,self)end end function rtk.Entry:_caret_from_mouse_event(event)local calc=self.calc local iconw=calc.icon and(calc.icon.w*rtk.scale.value/calc.icon.density+calc.spacing)or 0 local relx=self._loffset+event.x-self.clientx-iconw-self._clp for i=2,calc.value:len()+1 do local pos=self._positions[i] local width=pos-self._positions[i-1] if relx<=self._positions[i]-width/2 then return i-1 end end return calc.value:len()+1 end local function is_word_break_character(value,pos)local c=value:sub(pos,pos)return c ~='_' and c:match('[%c%p%s]')end function rtk.Entry:_get_word_left(spaces)local value=self.calc.value local caret=self.calc.caret if spaces then while caret>1 and is_word_break_character(value,caret-1)do caret=caret-1 end end while caret>1 and not is_word_break_character(value,caret-1)do caret=caret-1 end return caret end function rtk.Entry:_get_word_right(spaces)local value=self.calc.value local caret=self.calc.caret local len=value:len()while caret<=len and not is_word_break_character(value,caret)do caret=caret+1 end if spaces then while caret<=len and is_word_break_character(value,caret)do caret=caret+1 end end return caret end function rtk.Entry:select_all()self._selstart=1 self._selend=self.calc.value:len()+1 self._dirty_text=true self:queue_draw()end function rtk.Entry:select_range(a,b)local len=#self.calc.value if len==0 or not a then self._selstart=nil else b=b or a self._selstart=math.max(1,a)self._selend=b>0 and math.min(len+1,b+1)or math.max(self._selstart,len+b+2)end self._dirty_text=true self:queue_draw()end function rtk.Entry:get_selection_range()if self._selstart then return math.min(self._selstart,self._selend),math.max(self._selstart,self._selend)end end function rtk.Entry:_edit(insert,delete_selection,dela,delb,caret)local calc=self.calc local value=calc.value if delete_selection then dela,delb=self:get_selection_range()if dela and delb then local ndeleted=delb-dela caret=rtk.clamp(dela,1,#value)end end caret=caret or calc.caret if dela and delb then dela=rtk.clamp(dela,1,#value)delb=rtk.clamp(delb,1,#value+1)value=value:sub(1,dela-1)..value:sub(delb)self._dirty_positions=math.min(dela-1,self._dirty_positions or math.inf)end if insert then self._dirty_positions=math.min(caret-1,self._dirty_positions or math.inf)value=value:sub(0,caret-1)..insert..value:sub(caret)caret=caret+insert:len()end if value~=calc.value then caret=rtk.clamp(caret,1,#value+1)self:sync('value', value, nil, false)if caret~=calc.caret then self:sync('caret', caret)end self:_handle_change()self._dirty_view=true end end function rtk.Entry:delete_range(a,b)self:push_undo()self:_edit(nil,nil,a,b)end function rtk.Entry:delete()if self._selstart then self:push_undo()end self:_edit(nil,true)end function rtk.Entry:clear()if self.calc.value ~='' then self:push_undo()self:sync('value', '')end end function rtk.Entry:copy()if self._selstart then local a,b=self:get_selection_range()local text=self.calc.value:sub(a,b-1)if rtk.clipboard.set(text)then return text end end end function rtk.Entry:cut()local copied=self:copy()if copied then self:delete()end return copied end function rtk.Entry:paste()local str=rtk.clipboard.get()if str and str ~='' then self:push_undo()self:_edit(str,true)return str end end function rtk.Entry:insert(text)self:push_undo()self:_edit(text)end function rtk.Entry:undo()local calc=self.calc if self._history and #self._history>0 then local state=table.remove(self._history,#self._history)local value,caret value,caret,self._selstart,self._selend=table.unpack(state)self:sync('value', value)self:sync('caret', caret)return true else return false end end function rtk.Entry:push_undo()if not self._history then self._history={}end local calc=self.calc self._history[#self._history+1]={calc.value,calc.caret,self._selstart,self._selend}end function rtk.Entry:_handle_mousedown(event)local ok=rtk.Widget._handle_mousedown(self,event)if ok==false then return ok end if event.button==rtk.mouse.BUTTON_LEFT then local caret=self:_caret_from_mouse_event(event)self._selstart=nil self._dirty_text=true self._caretctr=0 self:sync('caret', caret)self:queue_draw()elseif event.button==rtk.mouse.BUTTON_RIGHT then if not self._popup then self._popup=rtk.NativeMenu(rtk.Entry.contextmenu)end local clipboard=rtk.clipboard.get()self._popup:item('undo').disabled = not self._history or #self._history == 0 self._popup:item('cut').disabled = not self._selstart self._popup:item('copy').disabled = not self._selstart self._popup:item('delete').disabled = not self._selstart self._popup:item('paste').disabled = not clipboard or clipboard == ''self._popup:item('select_all').disabled = #self.calc.value == 0 self._popup:open_at_mouse():done(function(item)if item then self[item.id](self)end end)end return true end function rtk.Entry:_handle_keypress(event)local ok=rtk.Widget._handle_keypress(self,event)if ok==false then return ok end local calc=self.calc local newcaret=nil local len=calc.value:len()local orig_caret=calc.caret local selecting=event.shift if event.keycode==rtk.keycodes.LEFT then if not selecting and self._selstart then newcaret=self._selstart elseif event.ctrl then newcaret=self:_get_word_left(true)else newcaret=math.max(1,calc.caret-1)end elseif event.keycode==rtk.keycodes.RIGHT then if not selecting and self._selstart then newcaret=self._selend elseif event.ctrl then newcaret=self:_get_word_right(true)else newcaret=math.min(calc.caret+1,len+1)end elseif event.keycode==rtk.keycodes.HOME then newcaret=1 elseif event.keycode==rtk.keycodes.END then newcaret=calc.value:len()+1 elseif event.keycode==rtk.keycodes.DELETE then if self._selstart then self:delete()else if event.ctrl then self:push_undo()self:_edit(nil,false,calc.caret,self:_get_word_right(true)-1)elseif calc.caret<=len then self:_edit(nil,false,calc.caret,calc.caret+1)end end elseif event.keycode==rtk.keycodes.BACKSPACE then if calc.caret>=1 then if self._selstart then self:delete()else if event.ctrl then self:push_undo()local caret=self:_get_word_left(true)self:_edit(nil,false,caret,calc.caret,caret)elseif calc.caret>1 then local caret=calc.caret-1 self:_edit(nil,false,caret,caret+1,caret)end end end elseif event.char and not event.ctrl then if self._selstart then self:push_undo()end self:_edit(event.char,true)selecting=false elseif event.ctrl and event.char and not event.shift then if event.char=='a' and len > 0 then self:select_all()selecting=nil elseif event.char=='c' then self:copy()return true elseif event.char=='x' then self:cut()elseif event.char=='v' then self:paste()elseif event.char=='z' then self:undo()selecting=nil end else return ok end if newcaret then self:sync('caret', newcaret)end if selecting then if not self._selstart then self._selstart=orig_caret end self._selend=calc.caret self._dirty_text=true elseif selecting==false and self._selstart then self._selstart=nil self._dirty_text=true end self._caretctr=0 log.debug2('keycode=%s char=%s caret=%s ctrl=%s shift=%s meta=%s alt=%s sel=%s-%s',event.keycode,event.char,calc.caret,event.ctrl,event.shift,event.meta,event.alt,self._selstart,self._selend )return true end function rtk.Entry:_get_touch_activate_delay(event)if self:focused(event)then return 0 else return rtk.Widget._get_touch_activate_delay(self,event)end end function rtk.Entry:_handle_dragstart(event)if not self:focused(event)or event.button~=rtk.mouse.BUTTON_LEFT then return end local draggable,droppable=self:ondragstart(self,event)if draggable==nil then self._selstart=self.calc.caret self._selend=self.calc.caret return true,false end return draggable,droppable end function rtk.Entry:_handle_dragmousemove(event)local ok=rtk.Widget._handle_dragmousemove(self,event)if ok==false then return ok end local selend=self:_caret_from_mouse_event(event)if selend==self._selend then return ok end self._selend=selend self:sync('caret', selend)self._dirty_text=true return ok end function rtk.Entry:_handle_click(event)local ok=rtk.Widget._handle_click(self,event)if ok==false or event.button~=rtk.mouse.BUTTON_LEFT then return ok end if event.time-self._last_doubleclick_time<0.7 then local last=rtk.mouse.last[event.button] local dx=last and math.abs(last.x-event.x)or 0 local dy=last and math.abs(last.y-event.y)or 0 if dx<3 and dy<3 then self:select_all()end self._last_doubleclick_time=0 elseif rtk.dnd.dragging~=self then self:select_range(nil)rtk.Widget.focus(self)end return ok end function rtk.Entry:_handle_doubleclick(event)local ok=rtk.Widget._handle_doubleclick(self,event)if ok==false or event.button~=rtk.mouse.BUTTON_LEFT then return ok end self._last_doubleclick_time=event.time local left=self:_get_word_left(false)local right=self:_get_word_right(true)self:sync('caret', right)self:select_range(left,right-1)return true end function rtk.Entry:_rendertext(x,y,event)self._font:set()self._backingstore:blit{src=gfx.dest,sx=x+self._clp,sy=y+self._ctp,mode=rtk.Image.FAST_BLIT }self._backingstore:pushdest()if self._selstart and self:focused(event)then local a,b=self:get_selection_range()self:setcolor(rtk.theme.entry_selection_bg)gfx.rect(self._positions[a]-self._loffset,0,self._positions[b]-self._positions[a],self._backingstore.h,1 )end self:setcolor(self.calc.textcolor)self._font:draw(self.calc.value,-self._loffset,rtk.os.mac and 1 or 0)self._backingstore:popdest()self._dirty_text=false end function rtk.Entry:_draw(offx,offy,alpha,event,clipw,cliph,cltargetx,cltargety,parentx,parenty)local calc=self.calc if offy~=self.offy or offx~=self.offx then self._dirty_text=true end rtk.Widget._draw(self,offx,offy,alpha,event,clipw,cliph,cltargetx,cltargety,parentx,parenty)local x,y=calc.x+offx,calc.y+offy local focused=self:focused(event)if(y+calc.h<0 or y>cliph or calc.ghost)and not focused then return false end if self.disabled then alpha=alpha*0.5 end local scale=rtk.scale.value local tp,rp,bp,lp=self._ctp,self._crp,self._cbp,self._clp self:_handle_drawpre(offx,offy,alpha,event)self:_draw_bg(offx,offy,alpha,event)if not self._dirty_text then gfx.x,gfx.y=x+lp,y+tp local r,g,b=gfx.getpixel()if self._lastbg_r~=r or self._lastbg_g~=g or self._lastbg_b~=b then self._lastbg_r,self._lastbg_g,self._lastbg_b=r,g,b self._dirty_text=true end end if self._dirty_positions then self:_calcpositions(self._dirty_positions)end if self._dirty_view or self._dirty_text then self:_calcview()end if self._dirty_text then self:_rendertext(x,y,event)end local amul=calc.alpha*alpha local icon=calc.icon if icon then local a=math.min(1,calc.icon_alpha*alpha+(focused and 0.2 or 0))icon:draw(x+lp,y+((calc.h+tp-bp)-icon.h*scale/icon.density)/2,a*amul,scale )lp=lp+icon.w*scale/icon.density+calc.spacing end self._backingstore:blit{sx=0,sy=0,sw=calc.w-lp-rp,sh=calc.h-tp-bp,dx=x+lp,dy=y+tp,alpha=amul,mode=rtk.Image.FAST_BLIT }if calc.placeholder and #calc.value==0 then self._font:set()self:setcolor(rtk.theme.entry_placeholder,alpha)self._font:draw(calc.placeholder,x+lp,y+tp+(rtk.os.mac and 1 or 0),calc.w-lp,calc.h-tp )end if focused then local showcursor=not self._selstart or(self._selend-self._selstart)==0 if not self._blinking and showcursor then self:_blink()end self:_draw_borders(offx,offy,alpha,calc.border_focused)if self._caretctr%32<16 and showcursor then local curx=x+self._positions[calc.caret]+lp-self._loffset if curx>x and curx<=x+calc.w-rp then self:setcolor(calc.textcolor,alpha)gfx.line(curx,y+tp,curx,y+calc.h-bp,0)end end else self._blinking=false if self.hovering then self:_draw_borders(offx,offy,alpha,calc.border_hover)else self:_draw_borders(offx,offy,alpha)end end self:_handle_draw(offx,offy,alpha,event)end function rtk.Entry:onchange(event)end function rtk.Entry:_handle_change(event)return self:onchange(event)end end)() __mod_rtk_text=(function() local rtk=__mod_rtk_core rtk.Text=rtk.class('rtk.Text', rtk.Widget)rtk.Text.static.WRAP_NONE=false rtk.Text.static.WRAP_NORMAL=true rtk.Text.static.WRAP_BREAK_WORD=2 rtk.Text.register{[1]=rtk.Attribute{alias='text'},text=rtk.Attribute{default='Text',reflow=rtk.Widget.REFLOW_FULL,},color=rtk.Attribute{reflow=rtk.Widget.REFLOW_NONE,default=rtk.Attribute.NIL,calculate=function(self,attr,value,target)if not value then local parentbg=self.parent and self.parent.calc.bg local luma=rtk.color.luma(self.calc.bg,parentbg or rtk.theme.bg)value=rtk.themes[luma > rtk.light_luma_threshold and 'light' or 'dark'].text end return {rtk.color.rgba(value)}end,},wrap=rtk.Attribute{default=rtk.Text.WRAP_NONE,reflow=rtk.Widget.REFLOW_FULL,calculate={['none']=rtk.Text.WRAP_NONE,['normal']=rtk.Text.WRAP_NORMAL,['break-word']=rtk.Text.WRAP_BREAK_WORD },},textalign=rtk.Attribute{default=nil,calculate=rtk.Reference('halign'),},overflow=false,spacing=rtk.Attribute{default=0,reflow=rtk.Widget.REFLOW_FULL,},font=rtk.Attribute{default=function(self,attr)return self._theme_font[1] end,reflow=rtk.Widget.REFLOW_FULL,},fontsize=rtk.Attribute{default=function(self,attr)return self._theme_font[2] end,reflow=rtk.Widget.REFLOW_FULL,},fontscale=rtk.Attribute{default=1.0,reflow=rtk.Widget.REFLOW_FULL,},fontflags=rtk.Attribute{default=function(self,attr)return self._theme_font[3] end },}function rtk.Text:initialize(attrs,...)self._theme_font=self._theme_font or rtk.theme.text_font or rtk.theme.default_font rtk.Widget.initialize(self,attrs,rtk.Text.attributes.defaults,...)self._font=rtk.Font()self._num_newlines=nil end function rtk.Text:__tostring_info()return self.text end function rtk.Text:_handle_attr(attr,value,oldval,trigger,reflow,sync)if attr == 'text' and reflow == rtk.Widget.REFLOW_DEFAULT and not self.calc.wrap then if self.w or(self.box and self.box[5])then local c=value:count('\n')if c==self._num_newlines then reflow=rtk.Widget.REFLOW_PARTIAL end self._num_newlines=c end end local ok=rtk.Widget._handle_attr(self,attr,value,oldval,trigger,reflow,sync)if ok==false then return ok end if self._segments and (attr == 'text' or attr == 'wrap' or attr == 'textalign' or attr == 'spacing') then self._segments.dirty=true elseif attr=='bg' and not self.color then self:attr('color', self.color, true, rtk.Widget.REFLOW_NONE)end return ok end function rtk.Text:_reflow(boxx,boxy,boxw,boxh,fillw,fillh,clampw,clamph,uiscale,viewport,window,greedyw,greedyh)local calc=self.calc calc.x,calc.y=self:_get_box_pos(boxx,boxy)self._font:set(calc.font,calc.fontsize,calc.fontscale,calc.fontflags)local w,h,tp,rp,bp,lp,minw,maxw,minh,maxh=self:_get_content_size(boxw,boxh,fillw,fillh,clampw,clamph,nil,greedyw,greedyh )local hpadding=lp+rp local vpadding=tp+bp local lmaxw=w or((clampw or(fillw and greedyw))and(boxw-hpadding)or math.inf)local lmaxh=h or((clamph or(fillh and greedyh))and(boxh-vpadding)or math.inf)local seg=self._segments if not seg or seg.boxw~=lmaxw or not seg.isvalid()then self._segments,self.lw,self.lh=self._font:layout(calc.text,lmaxw,lmaxh,calc.wrap~=rtk.Text.WRAP_NONE,self.textalign and calc.textalign or calc.halign,true,calc.spacing,calc.wrap==rtk.Text.WRAP_BREAK_WORD )end calc.w=(w and w+hpadding)or(fillw and greedyw and boxw)or math.min(clampw and boxw or math.inf,self.lw+hpadding)calc.h=(h and h+vpadding)or(fillh and greedyh and boxh)or math.min(clamph and boxh or math.inf,self.lh+vpadding)calc.w=math.ceil(rtk.clamp(calc.w,minw,maxw))calc.h=math.ceil(rtk.clamp(calc.h,minh,maxh))end function rtk.Text:_realize_geometry()local calc=self.calc local tp,rp,bp,lp=self:_get_padding_and_border()local lx,ly if calc.halign==rtk.Widget.LEFT then lx=lp elseif calc.halign==rtk.Widget.CENTER then lx=lp+math.max(0,calc.w-self.lw-lp-rp)/2 elseif calc.halign==rtk.Widget.RIGHT then lx=math.max(0,calc.w-self.lw-rp)end if calc.valign==rtk.Widget.TOP then ly=tp elseif calc.valign==rtk.Widget.CENTER then ly=tp+math.max(0,calc.h-self.lh-tp-bp)/2 elseif calc.valign==rtk.Widget.BOTTOM then ly=math.max(0,calc.h-self.lh-bp)end self._pre={tp=tp,rp=rp,bp=bp,lp=lp,lx=lx,ly=ly,}end function rtk.Text:_draw(offx,offy,alpha,event,clipw,cliph,cltargetx,cltargety,parentx,parenty)rtk.Widget._draw(self,offx,offy,alpha,event,clipw,cliph,cltargetx,cltargety,parentx,parenty)local calc=self.calc local x,y=calc.x+offx,calc.y+offy if y+calc.h<0 or y>cliph or calc.ghost then return end local pre=self._pre self:_handle_drawpre(offx,offy,alpha,event)self:_draw_bg(offx,offy,alpha,event)self:setcolor(calc.color,alpha)assert(self._segments)self._font:draw(self._segments,x+pre.lx,y+pre.ly,not calc.overflow and math.min(clipw-x,calc.w)-pre.lx-pre.rp or nil,not calc.overflow and math.min(cliph-y,calc.h)-pre.ly-pre.bp or nil )self:_draw_borders(offx,offy,alpha)self:_handle_draw(offx,offy,alpha,event)end end)() __mod_rtk_heading=(function() local rtk=__mod_rtk_core rtk.Heading=rtk.class('rtk.Heading', rtk.Text)rtk.Heading.register{color=rtk.Attribute{default=function(self,attr)return rtk.theme.heading or rtk.theme.text end },}function rtk.Heading:initialize(attrs,...)self._theme_font=self._theme_font or rtk.theme.heading_font or rtk.theme.default_font rtk.Text.initialize(self,attrs,self.class.attributes.defaults,...)end end)() __mod_rtk_imagebox=(function() local rtk=__mod_rtk_core local log=__mod_rtk_log rtk.ImageBox=rtk.class('rtk.ImageBox', rtk.Widget)rtk.ImageBox.register{[1]=rtk.Attribute{alias='image'},image=rtk.Attribute{calculate=rtk.Entry.attributes.icon.calculate,reflow=rtk.Widget.REFLOW_FULL,},scale=rtk.Attribute{default=rtk.Attribute.NIL,reflow=rtk.Widget.REFLOW_FULL,},aspect=rtk.Attribute{reflow=rtk.Widget.REFLOW_FULL,},}function rtk.ImageBox:initialize(attrs,...)rtk.Widget.initialize(self,attrs,self.class.attributes.defaults,...)end function rtk.ImageBox:_handle_attr(attr,value,oldval,trigger,reflow,sync)local ret=rtk.Widget._handle_attr(self,attr,value,oldval,trigger,reflow,sync)if ret==false then return ret end if attr=='image' and value then self._last_reflow_scale=nil elseif attr == 'bg' and type(self.image) == 'string' then self:attr('image', self.image, true, rtk.Widget.REFLOW_NONE)end return ret end function rtk.ImageBox:_reflow(boxx,boxy,boxw,boxh,fillw,fillh,clampw,clamph,uiscale,viewport,window,greedyw,greedyh)local calc=self.calc calc.x,calc.y=self:_get_box_pos(boxx,boxy)local w,h,tp,rp,bp,lp,minw,maxw,minh,maxh=self:_get_content_size(boxw,boxh,fillw,fillh,clampw,clamph,self.scale or 1,greedyw,greedyh )local dstw,dsth=0,0 local hpadding=lp+rp local vpadding=tp+bp local image=calc.image if image then if uiscale~=self._last_reflow_scale then image:refresh_scale()self._last_reflow_scale=uiscale end local scale=(self.scale or 1)*uiscale/image.density local native_aspect=image.w/image.h local aspect=calc.aspect or native_aspect dstw=(w and w-hpadding)or((fillw and greedyw)and boxw-hpadding)dsth=(h and h-vpadding)or((fillh and greedyh)and boxh-vpadding)local constrain=self.scale==nil and not w and not h if dstw and not dsth then dsth=math.min(clamph and boxw or math.inf,dstw)/aspect elseif not dstw and dsth then dstw=math.min(clampw and boxh or math.inf,dsth)*aspect elseif not dstw and not dsth then dstw=image.w*scale/(native_aspect/aspect)dsth=image.h*scale end if constrain then if dstw+hpadding>boxw then dstw=boxw-hpadding dsth=dstw/aspect end if dsth+vpadding>boxh then dsth=boxh-vpadding dstw=dsth*aspect end end self.iscale=dstw/image.w calc.aspect=aspect calc.scale=self.iscale else self.iscale=1.0 end self.iw=math.round(math.max(0,dstw))self.ih=math.round(math.max(0,dsth))calc.w=(fillw and greedyw and boxw)or math.min(clampw and boxw or math.inf,self.iw+hpadding)calc.h=(fillh and greedyh and boxh)or math.min(clamph and boxh or math.inf,self.ih+vpadding)calc.w=math.ceil(rtk.clamp(calc.w,minw,maxw))calc.h=math.ceil(rtk.clamp(calc.h,minh,maxh))end function rtk.ImageBox:_realize_geometry()local calc=self.calc local tp,rp,bp,lp=self:_get_padding_and_border()local ix,iy if calc.halign==rtk.Widget.LEFT then ix=lp elseif calc.halign==rtk.Widget.CENTER then ix=lp+math.max(0,calc.w-self.iw-lp-rp)/2 elseif calc.halign==rtk.Widget.RIGHT then ix=math.max(0,calc.w-self.iw-rp)end if calc.valign==rtk.Widget.TOP then iy=tp elseif calc.valign==rtk.Widget.CENTER then iy=tp+math.max(0,calc.h-self.ih-tp-bp)/2 elseif calc.valign==rtk.Widget.BOTTOM then iy=math.max(0,calc.h-self.ih-bp)end self._pre={ix=ix,iy=iy}end function rtk.ImageBox:_draw(offx,offy,alpha,event,clipw,cliph,cltargetx,cltargety,parentx,parenty)rtk.Widget._draw(self,offx,offy,alpha,event,clipw,cliph,cltargetx,cltargety,parentx,parenty)local calc=self.calc local x,y=calc.x+offx,calc.y+offy if y+calc.h<0 or y>cliph or calc.ghost then return end local pre=self._pre self:_handle_drawpre(offx,offy,alpha,event)self:_draw_bg(offx,offy,alpha,event)if calc.image then calc.image:blit{dx=x+pre.ix,dy=y+pre.iy,dw=self.iw,dh=self.ih,alpha=calc.alpha*alpha,clipw=calc.w,cliph=calc.h,}end self:_draw_borders(offx,offy,alpha)self:_handle_draw(offx,offy,alpha,event)end end)() __mod_rtk_optionmenu=(function() local rtk=__mod_rtk_core rtk.OptionMenu=rtk.class('rtk.OptionMenu', rtk.Button)rtk.OptionMenu.static._icon=nil rtk.OptionMenu.register{[1]=rtk.Attribute{alias='menu'},menu=nil,icononly=rtk.Attribute{default=false,reflow=rtk.Widget.REFLOW_FULL,},selected=nil,selected_index=nil,selected_id=nil,selected_item=nil,icon=rtk.Attribute{default=function(self)return rtk.OptionMenu.static._icon end,},iconpos=rtk.Widget.RIGHT,tagged=true,lpadding=10,rpadding=rtk.Attribute{default=function(self)return(self.icononly or self.circular)and self.lpadding or 7 end },tagalpha=0.15,}function rtk.OptionMenu:initialize(attrs,...)if not rtk.OptionMenu._icon then local icon=rtk.Image(13,17)icon:pushdest(icon.id)rtk.color.set(rtk.theme.text)gfx.triangle(2,6,10,6,6,10)icon:popdest()rtk.OptionMenu.static._icon=icon end rtk.Button.initialize(self,attrs,self.class.attributes.defaults,...)self._menu=rtk.NativeMenu()self:_handle_attr('menu', self.calc.menu)self:_handle_attr('icononly', self.calc.icononly)end function rtk.OptionMenu:_reflow_get_max_label_size(boxw,boxh)local segments,lw,lh=rtk.Button._reflow_get_max_label_size(self,boxw,boxh)local w,h=0,0 for item in self._menu:items()do local item_w,item_h=gfx.measurestr(item.altlabel or item.label)w=math.max(w,item_w)h=math.max(h,item_h)end return segments,rtk.clamp(w,lw,boxw),rtk.clamp(h,lh,boxh)end function rtk.OptionMenu:select(value,trigger)return self:attr('selected', value, trigger)end function rtk.OptionMenu:_handle_attr(attr,value,oldval,trigger,reflow,sync)local ok=rtk.Button._handle_attr(self,attr,value,oldval,trigger,reflow,sync)if ok==false then return ok end if attr=='menu' then self._menu:set(value)if not self.calc.icononly and not self.selected then self:sync('label', '')elseif self.selected then self:_handle_attr('selected', self.selected, self.selected, true)end elseif attr=='selected' then local item=self._menu:item(value)self.selected_item=item if item then if not self.calc.icononly then self:sync('label', item.altlabel or item.label)end self.selected_index=item.index self.selected_id=item.id rtk.Button.onattr(self,attr,value,oldval,trigger)else self.selected_index=nil self.selected_id=nil if not self.calc.icononly then self:sync('label', '')end end local last=self._menu:item(oldval)if value~=oldval and trigger~=false then self:onchange(item,last)self:onselect(item,last)elseif trigger then self:onselect(item,last)end end return true end function rtk.OptionMenu:open()assert(self.menu, 'menu attribute was not set on OptionMenu')self._menu:open_at_widget(self):done(function(item)if item then self:sync('selected', item.id or item.index, nil, true)end end)end function rtk.OptionMenu:_handle_mousedown(event)local ok=rtk.Button._handle_mousedown(self,event)if ok==false then return ok end self:open()return true end function rtk.OptionMenu:onchange(item,lastitem)end function rtk.OptionMenu:onselect(item,lastitem)end end)() __mod_rtk_checkbox=(function() local rtk=__mod_rtk_core rtk.CheckBox=rtk.class('rtk.CheckBox', rtk.Button)rtk.CheckBox.static._icon_unchecked=nil rtk.CheckBox.static.DUALSTATE=0 rtk.CheckBox.static.TRISTATE=1 rtk.CheckBox.static.UNCHECKED=false rtk.CheckBox.static.CHECKED=true rtk.CheckBox.static.INDETERMINATE=2 function rtk.CheckBox.static._make_icons()local w,h=18,18 local wp,hp=2,2 local colors if rtk.theme.dark then colors={border={1,1,1,0.90},fill={1,1,1,1},check={0,0,0,1},checkaa={0.4,0.4,0.4,1},iborder={1,1,1,0.92},}else colors={border={0,0,0,0.90},fill={0,0,0,1},check={1,1,1,1},checkaa={0.6,0.6,0.6,1},iborder={0,0,0,0.92},}end local icon=rtk.Image(w,h)icon:pushdest()rtk.color.set(colors.border)rtk.gfx.roundrect(wp,hp,w-wp*2,h-hp*2,2,1)gfx.rect(wp+1,hp+1,w-wp*2-2,h-hp*2-2,0)icon:popdest()rtk.CheckBox.static._icon_unchecked=icon icon=rtk.Image(w,h)icon:pushdest()rtk.color.set(colors.fill)rtk.gfx.roundrect(wp,hp,w-wp*2,h-hp*2,2,1)rtk.color.set(colors.fill)gfx.rect(wp+1,hp+1,w-wp*2-2,h-hp*2-2,1)rtk.color.set(colors.checkaa)gfx.x=wp+3 gfx.y=hp+6 gfx.lineto(wp+5,hp+9)gfx.lineto(wp+10,hp+3)rtk.color.set(colors.check)gfx.x=wp+2 gfx.y=hp+6 gfx.lineto(wp+5,hp+10)gfx.lineto(wp+11,hp+3)icon:popdest()rtk.CheckBox.static._icon_checked=icon icon=rtk.CheckBox.static._icon_unchecked:clone()icon:pushdest()rtk.color.set(colors.iborder)gfx.rect(wp+3,hp+3,w-wp*2-6,h-hp*2-6)rtk.color.set(colors.fill)gfx.rect(wp+4,hp+4,w-wp*2-8,h-hp*2-8,1)icon:popdest()rtk.CheckBox.static._icon_intermediate=icon rtk.CheckBox.static._icon_hover=rtk.CheckBox.static._icon_unchecked:clone():recolor(rtk.theme.accent)end rtk.CheckBox.register{type=rtk.Attribute{default=rtk.CheckBox.DUALSTATE,calculate={dualstate=rtk.CheckBox.DUALSTATE,tristate=rtk.CheckBox.TRISTATE },},label=nil,value=rtk.Attribute{default=rtk.CheckBox.UNCHECKED,calculate={[rtk.Attribute.NIL]=rtk.CheckBox.UNCHECKED,['true']=rtk.CheckBox.static.CHECKED,checked=rtk.CheckBox.static.CHECKED,['false']=rtk.CheckBox.static.UNCHECKED,unchecked=rtk.CheckBox.static.UNCHECKED,indeterminate=rtk.CheckBox.static.INDETERMINATE,}},icon=rtk.Attribute{default=function(self,attr)return self._value_map[rtk.CheckBox.UNCHECKED] end,},surface=false,valign=rtk.Widget.TOP,wrap=true,tpadding=0,rpadding=0,lpadding=0,bpadding=0,}function rtk.CheckBox:initialize(attrs,...)if rtk.CheckBox.static._icon_unchecked==nil then rtk.CheckBox._make_icons()end self._value_map={[rtk.CheckBox.UNCHECKED]=rtk.CheckBox._icon_unchecked,[rtk.CheckBox.CHECKED]=rtk.CheckBox._icon_checked,[rtk.CheckBox.INDETERMINATE]=rtk.CheckBox._icon_intermediate }rtk.Button.initialize(self,attrs,self.class.attributes.defaults,...)self:_handle_attr('value', self.calc.value)end function rtk.CheckBox:_handle_click(event)local ret=rtk.Button._handle_click(self,event)if ret==false then return ret end self:toggle()return ret end function rtk.CheckBox:_handle_attr(attr,value,oldval,trigger,reflow,sync)local ret=rtk.Button._handle_attr(self,attr,value,oldval,trigger,reflow,sync)if ret~=false then if attr=='value' then self.calc.icon=self._value_map[value] or self._value_map[rtk.CheckBox.UNCHECKED] if trigger then self:onchange()end end end return ret end function rtk.CheckBox:_draw_icon(x,y,hovering,alpha)rtk.Button._draw_icon(self,x,y,hovering,alpha)if hovering then rtk.CheckBox._icon_hover:draw(x,y,alpha,rtk.scale.value)end end function rtk.CheckBox:toggle()local value=self.calc.value if self.calc.type==rtk.CheckBox.DUALSTATE then if value==rtk.CheckBox.CHECKED then value=rtk.CheckBox.UNCHECKED else value=rtk.CheckBox.CHECKED end else if value==rtk.CheckBox.CHECKED then value=rtk.CheckBox.INDETERMINATE elseif value==rtk.CheckBox.INDETERMINATE then value=rtk.CheckBox.UNCHECKED else value=rtk.CheckBox.CHECKED end end self:sync('value', value)return self end function rtk.CheckBox:onchange()end end)() __mod_rtk_application=(function() local rtk=__mod_rtk_core rtk.Application=rtk.class('rtk.Application', rtk.VBox)rtk.Application.register{status=rtk.Attribute{reflow=rtk.Widget.REFLOW_NONE },statusbar=nil,toolbar=nil,screens=nil,}function rtk.Application:initialize(attrs,...)self.screens={stack={},}self.toolbar=rtk.HBox{bg=rtk.theme.bg,spacing=0,z=110,}self.toolbar:add(rtk.HBox.FLEXSPACE)self.statusbar=rtk.HBox{bg=rtk.theme.bg,lpadding=10,tpadding=5,bpadding=5,rpadding=10,z=110,}self.statusbar.text = self.statusbar:add(rtk.Text{color=rtk.theme.text_faded, text=""}, {fillw=true})rtk.VBox.initialize(self,attrs,self.class.attributes.defaults,...)self:add(self.toolbar,{minw=150,bpadding=2})self:add(rtk.VBox.FLEXSPACE)self._content_position=#self.children self:add(self.statusbar,{fillw=true})self:_handle_attr('status', self.calc.status)end function rtk.Application:_handle_attr(attr,value,oldval,trigger,reflow,sync)local ok=rtk.VBox._handle_attr(self,attr,value,oldval,trigger,reflow,sync)if ok==false then return ok end if attr=='status' then self.statusbar.text:attr('text', value or ' ')end return ok end function rtk.Application:add_screen(screen,name)assert(type(screen)=='table' and screen.init, 'screen must be a table containing an init() function')name=name or screen.name assert(name, 'screen is missing name')assert(not self.screens[name], string.format('screen "%s" was already added', name))local widget=screen.init(self,screen)if widget then assert(rtk.isa(widget, rtk.Widget), 'the return value from screen.init() must be type rtk.Widget (or nil)')screen.widget=widget else assert(rtk.isa(screen.widget, rtk.Widget), 'screen must contain a "widget" field of type rtk.Widget')end screen.name=name self.screens[name]=screen if not screen.toolbar then screen.toolbar=rtk.Spacer{h=0}end self.toolbar:insert(1,screen.toolbar,{minw=50})screen.toolbar:hide()screen.widget:hide()if #self.screens.stack==0 then self:replace_screen(screen)end end function rtk.Application:_show_screen(screen)screen=type(screen)=='table' and screen or self.screens[screen] for _,s in ipairs(self.screens.stack)do s.widget:hide()if s.toolbar then s.toolbar:hide()end end assert(screen, 'screen not found, was add_screen() called?')if screen then if screen.update then screen.update(self,screen)end if screen.widget.scrollto then screen.widget:scrollto(0,0)end screen.widget:show()self:replace(self._content_position,screen.widget,{expand=1,fillw=true,fillh=true,minw=screen.minw })screen.toolbar:show()end self:attr('status', nil)end function rtk.Application:push_screen(screen)screen=type(screen)=='table' and screen or self.screens[screen] assert(screen, 'screen not found, was add_screen() called?')if screen and #self.screens.stack>0 and self:current_screen()~=screen then self:_show_screen(screen)self.screens.stack[#self.screens.stack+1]=screen end end function rtk.Application:pop_screen()if #self.screens.stack>1 then self:_show_screen(self.screens.stack[#self.screens.stack-1])table.remove(self.screens.stack)return true else return false end end function rtk.Application:replace_screen(screen,idx)screen=type(screen)=='table' and screen or self.screens[screen] assert(screen, 'screen not found, was add_screen() called?')local last=#self.screens.stack idx=idx or last if idx==0 then idx=1 end if idx>=last then self:_show_screen(screen)elseif screen.update then screen.update(self,screen)end self.screens.stack[idx]=screen end function rtk.Application:current_screen()local n=#self.screens.stack if n>0 then return self.screens.stack[#self.screens.stack] end end end)() __mod_rtk_slider=(function() local rtk=__mod_rtk_core local log=__mod_rtk_log rtk.Slider=rtk.class('rtk.Slider', rtk.Widget)rtk.Slider.static.TICKS_NEVER=0 rtk.Slider.static.TICKS_ALWAYS=1 rtk.Slider.static.TICKS_WHEN_ACTIVE=2 rtk.Slider.register{[1]=rtk.Attribute{alias='value'},value=rtk.Attribute{default=0,priority=true,reflow=rtk.Widget.REFLOW_NONE,calculate=function(self,attr,value,target)return type(value)=='table' and value or {value}end,set=function(self,attr,value,calculated,target)self._use_scalar_value=type(value) ~='table'for i=1,#calculated do calculated[i]=rtk.clamp(tonumber(calculated[i]),target.min,target.max)if not self._thumbs[i] then self._thumbs[i]={idx=i,radius=0,radius_target=0}end end for i=#calculated+1,#self._thumbs do self._thumbs[i]=nil end target.value=calculated end },color=rtk.Attribute{type='color',default=function(self,attr)return rtk.theme.slider end,calculate=rtk.Reference('bg'),},trackcolor=rtk.Attribute{type='color',default=function(self,attr)return rtk.theme.slider_track end,calculate=rtk.Reference('bg'),},thumbsize=rtk.Attribute{default=6,reflow=rtk.Widget.REFLOW_FULL,},thumbcolor=rtk.Attribute{type='color',},ticklabels=rtk.Attribute{reflow=rtk.Widget.REFLOW_FULL,},ticklabelcolor=rtk.Attribute {type='color',default=function(self,attr)return rtk.theme.slider_tick_label or rtk.theme.text end,},spacing=rtk.Attribute{default=2,reflow=rtk.Widget.REFLOW_FULL,},ticks=rtk.Attribute{default=rtk.Slider.TICKS_NEVER,calculate={never=rtk.Slider.TICKS_NEVER,always=rtk.Slider.TICKS_ALWAYS,['when-active']=rtk.Slider.TICKS_WHEN_ACTIVE,['false']=rtk.Slider.TICKS_NEVER,[false]=rtk.Slider.TICKS_NEVER,['true']=rtk.Slider.TICKS_ALWAYS,[true]=rtk.Slider.TICKS_ALWAYS,},set=function(self,attr,value,calculated,target)self._tick_alpha=calculated==rtk.Slider.TICKS_ALWAYS and 1 or 0 target.ticks=calculated end,},ticksize=rtk.Attribute{default=4,reflow=rtk.Widget.REFLOW_FULL,},tracksize=rtk.Attribute{default=2,reflow=rtk.Widget.REFLOW_FULL,},min=0,max=100,step=rtk.Attribute{type='number',calculate=function(self,attr,value,target)return value and value>0 and value end,},font=rtk.Attribute{default=function(self,attr)return self._theme_font[1] end,reflow=rtk.Widget.REFLOW_FULL,},fontsize=rtk.Attribute{default=function(self,attr)return self._theme_font[2] end,reflow=rtk.Widget.REFLOW_FULL,},fontscale=rtk.Attribute{default=1.0,reflow=rtk.Widget.REFLOW_FULL },fontflags=rtk.Attribute{default=function(self,attr)return self._theme_font[3] end },focused_thumb_index=1,autofocus=true,scroll_on_drag=false,}function rtk.Slider:initialize(attrs,...)self._thumbs={}self._tick_alpha=0 self._hovering_thumb=nil self._font=rtk.Font()self._theme_font=rtk.theme.slider_font or rtk.theme.default_font rtk.Widget.initialize(self,attrs,rtk.Slider.attributes.defaults,...)end function rtk.Slider:_handle_attr(attr,value,oldval,trigger,reflow,sync)local ok=rtk.Widget._handle_attr(self,attr,value,oldval,trigger,reflow,sync)if ok==false then return ok end if attr=='value' then self:onchange()elseif self._label_segments and attr=='ticklabels' then self._label_segments=nil end end function rtk.Slider:_reflow(boxx,boxy,boxw,boxh,fillw,fillh,clampw,clamph,uiscale,viewport,window,greedyw,greedyh)local calc=self.calc calc.x,calc.y=self:_get_box_pos(boxx,boxy)local w,h,tp,rp,bp,lp,minw,maxw,minh,maxh=self:_get_content_size(boxw,boxh,fillw,fillh,clampw,clamph,nil,greedyw,greedyh )local hpadding=lp+rp local vpadding=tp+bp local lh=0 local segments=self._label_segments self._font:set(calc.font,calc.fontsize,calc.fontscale,calc.fontflags)if calc.step and calc.ticklabels and(not segments or not segments[1].isvalid())then local lmaxw=(clampw or(fillw and greedyw))and(boxw-hpadding)or w or math.inf local lmaxh=(clamph or(fillh and greedyh))and(boxh-vpadding)or h or math.inf segments={}for n=1,#calc.ticklabels do local label=calc.ticklabels[n] or ''local s,w,h=self._font:layout(label,lmaxw,lmaxh,false,rtk.Widget.CENTER,true,0,false )s.w=w s.h=h segments[#segments+1]=s lh=math.max(h,lh)end lh=lh+calc.spacing self._label_segments=segments end self.lh=lh minw=math.max(minw or 0,math.max(calc.minw or 0,#calc.value*calc.thumbsize*2)*rtk.scale.value)minh=math.max(minh or 0,math.max(calc.minh or 0,calc.thumbsize*2,calc.tracksize)*rtk.scale.value)local size=math.max(calc.thumbsize*2,calc.ticksize,calc.tracksize)*rtk.scale.value calc.w=w and(w+hpadding)or(greedyw and boxw or 50)calc.h=h and(h+vpadding)or(size+self.lh+vpadding)calc.w=math.ceil(rtk.clamp(calc.w,minw,maxw))calc.h=math.ceil(rtk.clamp(calc.h,minh,maxh))return not w,false end function rtk.Slider:_realize_geometry()local calc=self.calc local tp,rp,bp,lp=self:_get_padding_and_border()local scale=rtk.scale.value local track={x=calc.x+lp+calc.thumbsize*scale,y=calc.y+tp+((calc.h-tp-bp-self.lh)-calc.tracksize*scale)/2,w=calc.w-lp-rp-calc.thumbsize*2*scale,h=calc.tracksize*scale,}local ticks if calc.step then ticks={distance=track.w/((calc.max-calc.min)/calc.step),size=calc.ticksize*scale,}ticks.offset=(ticks.size-track.h)/2 for x=track.x,track.x+track.w+1,ticks.distance do ticks[#ticks+1]={x-ticks.offset,track.y-ticks.offset}end if calc.ticklabels then local ly=track.y+calc.tracksize+(calc.spacing+calc.thumbsize)*scale for n,segments in ipairs(self._label_segments)do local tick=ticks[n] if not tick then break end segments.x=tick[1] local offset=segments.w-ticks.size if n==#ticks then segments.x=segments.x-offset elseif n>1 then segments.x=segments.x-offset/2 end segments.y=ly end end end self._pre={tp=tp,rp=rp,bp=bp,lp=lp,track=track,ticks=ticks,}for idx=1,#self._thumbs do self._thumbs[idx].value=nil end end function rtk.Slider:_get_thumb(idx)assert(self._pre, '_get_thumb() called before reflow')local thumb=self._thumbs[idx] local track=self._pre.track local calc=self.calc local value=calc.value[idx] if thumb.value~=value then thumb.pos=track.w*(value-calc.min)/(calc.max-calc.min)thumb.value=value end local c=self:calc('value')if c~=value then thumb.pos_final=track.w*(c[idx]-calc.min)/(calc.max-calc.min)else thumb.pos_final=thumb.pos end return thumb end function rtk.Slider:_get_nearest_thumb(clientx,clienty)local trackx=self.clientx+self._pre.lp local tracky=self.clienty+self._pre.tp local candidate=nil local candidate_distance=nil for i=1,#self._thumbs do local thumb=self:_get_thumb(i)local delta=clientx-trackx-thumb.pos local distance=math.abs(delta)if not candidate or(distance0)then candidate=thumb candidate_distance=distance end end return candidate end function rtk.Slider:_clamp_value_to_step(v)local calc=self.calc local step=calc.step return rtk.clamp(step and(math.round(v/step)*step)or v,calc.min,calc.max)end function rtk.Slider:_set_thumb_value(thumbidx,value,animate,fast)value=self:_clamp_value_to_step(value)local current=self:calc('value')if current[thumbidx]==value then return false end local newval=self._use_scalar_value and value or table.shallow_copy(current,{[thumbidx]=value})if animate==false then self:cancel_animation('value')self:sync('value', newval)else self:sync('value', newval, current)local duration=fast and 0.25 or 0.4 self:animate{'value', dst=newval, doneval=newval, duration=duration, easing='out-expo'}end return true end function rtk.Slider:_set_thumb_value_with_crossover(idx,value,animate,event)local newidx local calc=self.calc if idx>1 and valuecalc.value[idx+1] then newidx=idx+1 end if newidx then self:_set_thumb_value(idx,calc.value[newidx],false)self.focused_thumb_index=newidx self._hovering_thumb=newidx self:_animate_thumb_overlays(event,nil,true)end local changed=self:_set_thumb_value(self.focused_thumb_index,value,animate,event.type~=rtk.Event.KEY)return changed,self.focused_thumb_index end function rtk.Slider:_is_mouse_over(clparentx,clparenty,event)if not self.window or not self.window.in_window then self._hovering_thumb=nil return false end local calc=self.calc local pre=self._pre local y=calc.y+clparenty+pre.tp local track=pre.track local trackx=track.x+clparentx local tracky=track.y+clparenty local radius=20*rtk.scale.value if not event:is_widget_pressed(self)then self._hovering_thumb=nil if rtk.point_in_box(event.x,event.y,trackx-radius,y-radius,calc.w+radius*2,calc.h+radius*2)then for i=1,#self._thumbs do local thumb=self:_get_thumb(i)if rtk.point_in_circle(event.x,event.y,trackx+thumb.pos,tracky,radius)then self._hovering_thumb=i break end end else return false end end return self._hovering_thumb or rtk.point_in_box(event.x,event.y,trackx,y-calc.thumbsize,calc.w,calc.h+calc.thumbsize*2)end function rtk.Slider:_handle_mouseleave(event)local ok=rtk.Widget._handle_mouseleave(self,event)if ok==false then return ok end self:_animate_thumb_overlays(event)return ok end function rtk.Slider:_handle_mousedown(event)local ok=rtk.Widget._handle_mousedown(self,event)if ok==false then return ok end local thumb=self:_get_nearest_thumb(event.x,event.y)self.focused_thumb_index=thumb.idx if not self._hovering_thumb then local value=self:_get_value_from_offset(event.x-self.clientx-self.calc.thumbsize)self:_set_thumb_value(thumb.idx,value,true,true)else self._hovering_thumb=thumb.idx end self:_animate_thumb_overlays(event)self:_animate_ticks(true)return true end function rtk.Slider:_handle_mouseup(event)local ok=rtk.Widget._handle_mouseup(self,event)self:_animate_thumb_overlays(event,nil,true)self:_animate_ticks(false)return ok end function rtk.Slider:_handle_dragstart(event,x,y,t)local draggable,droppable=self:ondragstart(self,event,x,y,t)if draggable~=nil then return draggable,droppable end local thumb=self:_get_nearest_thumb(x,y)self.focused_thumb_index=thumb.idx self:_animate_thumb_overlays(event,nil,true)return {startx=x,starty=y,thumbidx=thumb.idx},false end function rtk.Slider:_handle_dragmousemove(event,arg)local ok=rtk.Widget._handle_dragmousemove(self,event)if ok==false or event.simulated then return ok end if not arg.startpos then local thumb=self:_get_thumb(arg.thumbidx)arg.startpos=thumb.pos_final end local offx=(event.x-arg.startx)if arg.fine then offx=math.ceil(offx*0.2)end local v=self:_get_value_from_offset(offx+arg.startpos)local value_changed value_changed,arg.thumbidx=self:_set_thumb_value_with_crossover(arg.thumbidx,v,self.calc.step~=nil,event)if(event.shift and value_changed)or(event.shift~=arg.fine)then arg.startx=event.x arg.starty=event.y arg.startpos=nil end arg.fine=event.shift event:set_handled(self)return true end function rtk.Slider:_handle_dragend(event,dragarg)self:_animate_ticks(false)end function rtk.Slider:_handle_mousemove(event)self:_animate_thumb_overlays(event)end function rtk.Slider:_handle_focus(event,context)self:_animate_thumb_overlays(event,true)return rtk.Widget._handle_focus(self,event,context)end function rtk.Slider:_handle_blur(event,other)self._hovering_thumb=nil self:_animate_thumb_overlays(event,false)return rtk.Widget._handle_blur(self,event,other)end function rtk.Slider:_handle_keypress(event)local ok=rtk.Widget._handle_keypress(self,event)if ok==false or not self.focused_thumb_index then return ok end local calc=self.calc local value=calc.value[self.focused_thumb_index] local step=calc.step or(calc.max-calc.min)/10 if event.shift then step=step*3 elseif event.ctrl then step=step*2 end local newvalue if event.keycode==rtk.keycodes.LEFT or event.keycode==rtk.keycodes.DOWN then newvalue=value-step elseif event.keycode==rtk.keycodes.RIGHT or event.keycode==rtk.keycodes.UP then newvalue=value+step end if newvalue then self:_set_thumb_value_with_crossover(self.focused_thumb_index,newvalue,true,event)end return ok end function rtk.Slider:_animate_thumb_overlays(event,focused,force)if rtk.dnd.dragging and not force then return end if focused==nil then focused=self.window.is_focused and self:focused(event)end for i=1,#self._thumbs do local dst=nil local thumb=self:_get_thumb(i)if focused and thumb.idx==self.focused_thumb_index then if event and event.buttons~=0 then dst=32 else dst=20 end elseif thumb.idx==self._hovering_thumb then dst=20 elseif thumb.radius_target>0 then dst=0 end if dst~=nil and dst~=thumb.radius_target then thumb.radius_target=dst rtk.queue_animation{key=string.format('%s.thumb.%d.hover', self.id, thumb.idx),src=thumb.radius,dst=dst,duration=0.2,easing='out-sine',update=function(val)thumb.radius=val self:queue_draw()end,}end end end function rtk.Slider:_animate_ticks(on)local calc=self.calc if calc.step and calc.ticks==rtk.Slider.TICKS_WHEN_ACTIVE then local dst=on and 1 or 0 rtk.queue_animation{key=string.format('%s.ticks', self.id),src=self._tick_alpha,dst=dst,duration=0.2,easing='out-sine',update=function(val)self._tick_alpha=val self:queue_draw()end,}else self._ticks_alpha=(calc.ticks==rtk.Slider.TICKS_ALWAYS)and 1 or 0 end end function rtk.Slider:_get_value_from_offset(offx)local calc=self.calc local v=(offx*(calc.max-calc.min)/self._pre.track.w)+calc.min return self:_clamp_value_to_step(v)end function rtk.Slider:_draw(offx,offy,alpha,event,clipw,cliph,cltargetx,cltargety,parentx,parenty)rtk.Widget._draw(self,offx,offy,alpha,event,clipw,cliph,cltargetx,cltargety,parentx,parenty)local calc=self.calc local y=calc.y+offy if y+calc.h<0 or y>cliph or self.calc.ghost then return false end local scale=rtk.scale.value local pre=self._pre local track=pre.track local ticks=pre.ticks local trackx=track.x+offx local tracky=track.y+offy local thumby=tracky+(track.h/2)local tickalpha=0.6*self._tick_alpha*alpha*calc.alpha local drawticks=ticks and tickalpha>0 and not calc.disabled self:_handle_drawpre(offx,offy,alpha,event)self:_draw_bg(offx,offy,alpha,event)self:setcolor(calc.trackcolor,alpha)gfx.rect(trackx,tracky,track.w,track.h,1)local first_thumb_x,last_thumb_x if drawticks then first_thumb_x=trackx+self:_get_thumb(1).pos last_thumb_x=trackx+self:_get_thumb(#self._thumbs).pos self:setcolor('black', tickalpha)for i=1,#ticks do local x,y=table.unpack(ticks[i])if xlast_thumb_x then gfx.rect(offx+x,offy+y,ticks.size,ticks.size,1)end end end local thumbs={}local lastpos=0 for i=1,#self._thumbs do local thumb=self:_get_thumb(i)local thumbx=trackx+thumb.pos if not calc.disabled then if #self._thumbs==1 or i>1 then local segmentw=thumb.pos-lastpos self:setcolor(calc.color,alpha)gfx.rect(trackx+lastpos,tracky,segmentw,track.h,1)if drawticks then self:setcolor('white', tickalpha)for j=math.floor(lastpos/ticks.distance)+(i>1 and 2 or 1),#ticks do local x,y=table.unpack(ticks[j])if x>=track.x+thumb.pos then break end gfx.rect(offx+x,offy+y,ticks.size,ticks.size,1)end end end if thumb.radius>0 then self:setcolor(calc.thumbcolor or calc.color,0.25*alpha)gfx.circle(thumbx,thumby,thumb.radius*scale,1,1)end end thumbs[#thumbs+1]={thumbx,thumby}lastpos=thumb.pos end if not calc.disabled then self:setcolor(calc.thumbcolor or calc.color,alpha)end for i=1,#thumbs do local pos=thumbs[i] gfx.circle(pos[1],pos[2],calc.thumbsize*scale,1,1)end if self._label_segments then if not calc.disabled then self:setcolor(calc.ticklabelcolor,alpha)end for n,segments in ipairs(self._label_segments)do if not segments.x then break end self._font:draw(segments,offx+segments.x,offy+segments.y)end end self:_draw_borders(offx,offy,alpha)self:_handle_draw(offx,offy,alpha,event)end function rtk.Slider:onchange()end end)() __mod_rtk_xml=(function() local rtk=__mod_rtk_core local log=__mod_rtk_log local ATTR_PATTERNS={{'quoted', '^%s*([^>/%s=]+)%s*(=)%s*"([^"]+)"%s*(%/?)(%>?)'},{'quoted', "^%s*([^>/%s=]+)%s*(=)%s*'([^']+)'%s*(%/?)(%>?)"},{'mustache', '^%s*([^>/%s=]+)%s*(=)%s*({{)'},{'unquoted', '^%s*([^>/%s=]+)%s*(=)%s*([^%s/>]+)%s*(%/)(%>)'},{'unquoted', '^%s*([^>/%s=]+)%s*(=)%s*([^%s>]+)(%s*)(%>?)'},{'novalue', '^%s*([^>/%s]+)%s*()()(%/?)(%>?)'},}local ENTITIES={lt='<',gt='>',amp='&',apos="'",quot='"',nbsp=" ",}local function _unescape_entity(entity)local r=ENTITIES[entity] if not r and entity:sub(1, 1)=='#' then if entity:sub(2, 2)=='x' then r=utf8.char(tonumber(entity:sub(3),16))else r=utf8.char(tonumber(entity:sub(2)))end end return r end local function _unescape(s)return s and s:gsub('&([^;]+);', _unescape_entity)end local function _gettag(s,pos,elem,userdata,ontagstart,onattr)local a, b, preamble, close, tag, selfclose, term=s:find('^([^%<]*)%<%s*(%/?)%s*([^>/%s]+)%s*(%/?)%s*(%>?)', pos)if not a then return end pos=b+1 preamble=preamble:strip()if tag=='!--' then local finish=s:find('%-%->', pos)if finish then return finish+3,nil,false else return end elseif tag=='!DOCTYPE' then local finish=s:find(']>', pos)if finish then return finish+2,nil,false else log.warning('rtk.xml: invalid XML: DOCTYPE is not terminated')end elseif tag:sub(1, 8)=='![CDATA[' then local finish=s:find(']]>', pos)if finish then if elem then elem.content=tag:sub(9)..s:sub(pos,finish-1)else log.warning('rtk.xml: invalid XML: CDATA occurs outside an element')end return finish+3,nil,false else log.warning('rtk.xml: invalid XML: unterminated CDATA')if elem then elem.content=tag:sub(9)..s:sub(pos)end return end end if close=='/' then if not elem then log.warning('rtk.xml: invalid XML: unexpected end tag "%s"', tag)return elseif elem.tag~=tag then log.warning('rtk.xml: mismatched end tag "%s" -- expected "%s"', tag, elem.tag)return end if preamble ~="" then elem.content=(elem.content or '') .. _unescape(preamble)end return pos, elem, close=='/' and 1 elseif preamble ~="" and elem then elem.preamble=preamble end elem={tag=tag}if ontagstart and tag ~='?xml' then ontagstart(elem,userdata)end if term=='>' then return pos,elem,false end local attrs={}elem.attrs=attrs local attr,eq,value,whitespace while true do local pattern_type=nil for p=1,#ATTR_PATTERNS do local typ,pattern=table.unpack(ATTR_PATTERNS[p])a,b,attr,eq,value,selfclose,term=s:find(pattern,pos)if a then pattern_type=typ break end end if not pattern_type or b+1<=pos then break end if pattern_type=='mustache' then local finish=s:find('}}', b+1)if not finish then error(string.format('rtk.xml: terminating }} for expression not found for "%s"', attr), 3)end value=s:sub(b+1,finish-1)b=finish+2 a, b, whitespace, selfclose, term=s:find('^(%s*)(%/?)(%>?)', b)if #selfclose==0 and #term==0 and #whitespace>0 then error('rtk.xml: mustache expression has trailing characters -- perhaps quotes are needed?', 3)end elseif pattern_type=='novalue' then value=nil else value=_unescape(value)end local attrtable={name=attr,value=value,type=pattern_type}if onattr and tag ~='?xml' then onattr(elem,attrtable,userdata)end assert(attrtable.name, 'attribute is missing name')attrs[attrtable.name]=attrtable pos=b+1 if term=='>' then break end end return pos, elem, selfclose=='/' and 2 end function rtk.xmlparse(args)local xml,userdata,ontagstart,ontagend,onattr if type(args)=='string' then xml=args elseif type(args)=='table' then xml=args.xml or args[1] userdata=args.userdata or args[2] ontagstart=args.ontagstart ontagend=args.ontagend onattr=args.onattr else error('rtk.xmlparse() must receive either a string or table')end assert(type(xml)=='string', 'the XML document must be a string')local stack={}local root=nil local pos=1 while true do local last=stack[#stack] local newpos,elem,closed=_gettag(xml,pos,last,userdata,ontagstart,onattr)if not newpos or newpos<=pos then break end pos=newpos if closed and ontagend then ontagend(elem,userdata)end if closed==1 then table.remove(stack,#stack)elseif elem and elem.tag ~='?xml' then if #stack>0 then local current=stack[#stack] current[#current+1]=elem end if not closed then stack[#stack+1]=elem end end if not root then root=stack[#stack] end end return root end end)() rtk.log=__mod_rtk_log local function init()rtk.script_path=({reaper.get_action_context()})[2]:match('^.+[\\//]')rtk._image_paths.fallback={rtk.script_path}rtk.reaper_hwnd=reaper.GetMainHwnd()local ver=reaper.GetAppVersion():lower()if ver:find('x64') or ver:find('arm64') or ver:find('_64') or ver:find('aarch64') then rtk.os.bits=64 end local parts=ver:gsub('/.*', ''):split('.')rtk._reaper_version_major=tonumber(parts[1])local minor=parts[2] or ''local sepidx=minor:find('%D')if sepidx then rtk._reaper_version_prerelease=minor:sub(sepidx):gsub('^%+', '')minor=minor:sub(1,sepidx-1)end minor=tonumber(minor)or 0 rtk._reaper_version_minor=minor<100 and minor or minor/10 rtk.version.parse()rtk.scale._discover()if rtk.os.mac then rtk.font.multiplier=0.75 elseif rtk.os.linux then rtk.font.multiplier=0.7 end rtk.set_theme_by_bgcolor(rtk.color.get_reaper_theme_bg() or '#262626')rtk.theme.default=true reaper.atexit(function()if rtk.window and rtk.window.running then rtk.window:close()end rtk.log.flush()end)end init()return rtk end)() return rtk