mirror of
				https://github.com/kepler155c/opus
				synced 2025-10-31 15:43:00 +00:00 
			
		
		
		
	canvas overhaul
This commit is contained in:
		| @@ -91,7 +91,6 @@ local Browser = UI.Page { | ||||
| 	}, | ||||
| 	notification = UI.Notification { }, | ||||
| 	associations = UI.SlideOut { | ||||
| 		backgroundColor = colors.cyan, | ||||
| 		menuBar = UI.MenuBar { | ||||
| 			buttons = { | ||||
| 				{ text = 'Save',    event = 'save'    }, | ||||
| @@ -99,7 +98,7 @@ local Browser = UI.Page { | ||||
| 			}, | ||||
| 		}, | ||||
| 		grid = UI.ScrollingGrid { | ||||
| 			x = 2, ex = -6, y = 3, ey = -5, | ||||
| 			x = 2, ex = -6, y = 3, ey = -8, | ||||
| 			columns = { | ||||
| 				{ heading = 'Extension', key = 'name'  }, | ||||
| 				{ heading = 'Program',   key = 'value' }, | ||||
| @@ -114,8 +113,11 @@ local Browser = UI.Page { | ||||
| 			x = -4, y = 6, | ||||
| 			text = '-', event = 'remove_entry', help = 'Remove', | ||||
| 		}, | ||||
| 		[1] = UI.Window { | ||||
| 			x = 2, y = -6, ex = -6, ey = -3, | ||||
| 		}, | ||||
| 		form = UI.Form { | ||||
| 			x = 3, y = -3, ey = -2, | ||||
| 			x = 3, y = -5, ex = -7, ey = -3, | ||||
| 			margin = 1, | ||||
| 			manualControls = true, | ||||
| 			[1] = UI.TextEntry { | ||||
|   | ||||
| @@ -59,7 +59,6 @@ local page = UI.Page { | ||||
| 		[2] = UI.Tab { | ||||
| 			tabTitle = 'Output', | ||||
| 			output = UI.Embedded { | ||||
| 				visible = true, | ||||
| 				maxScroll = 1000, | ||||
| 				backgroundColor = colors.black, | ||||
| 			}, | ||||
|   | ||||
| @@ -74,7 +74,6 @@ local page = UI.Page { | ||||
| 		}, | ||||
| 	}, | ||||
| 	help = UI.SlideOut { | ||||
| 		backgroundColor = colors.cyan, | ||||
| 		x = 5, ex = -5, height = 8, y = -8, | ||||
| 		titleBar = UI.TitleBar { | ||||
| 			title = 'Network Help', | ||||
| @@ -82,7 +81,6 @@ local page = UI.Page { | ||||
| 		}, | ||||
| 		text = UI.TextArea { | ||||
| 			x = 2, y = 2, | ||||
| 			backgroundColor = colors.cyan, | ||||
| 			value = [[ | ||||
|  | ||||
| In order to connect to another computer: | ||||
|   | ||||
| @@ -26,6 +26,9 @@ local REGISTRY_DIR = 'usr/.registry' | ||||
| local DEFAULT_ICON = NFT.parse("\0308\0317\153\153\153\153\153\ | ||||
| \0307\0318\153\153\153\153\153\ | ||||
| \0308\0317\153\153\153\153\153") | ||||
| local TRANS_ICON = NFT.parse("\0302\0312\32\32\32\32\32\ | ||||
| \0302\0312\32\32\32\32\32\ | ||||
| \0302\0312\32\32\32\32\32") | ||||
|  | ||||
| -- overview | ||||
| local uid = _ENV.multishell.getCurrent() | ||||
| @@ -65,6 +68,7 @@ local function parseIcon(iconText) | ||||
| 			if icon.height > 3 or icon.width > 8 then | ||||
| 				error('Must be an NFT image - 3 rows, 8 cols max') | ||||
| 			end | ||||
| 			NFT.transparency(icon) | ||||
| 		end | ||||
| 		return icon | ||||
| 	end) | ||||
| @@ -89,6 +93,7 @@ function UI.VerticalTabBar:setParent() | ||||
| 		c.ox, c.oy = c.x, c.y | ||||
| 		c.ow = 8 | ||||
| 		c.width = 8 | ||||
| 		c:reposition(c.x, c.y, c.width, c.height) | ||||
| 	end | ||||
| end | ||||
|  | ||||
| @@ -114,7 +119,6 @@ local page = UI.Page { | ||||
| 	}, | ||||
| 	editor = UI.SlideOut { | ||||
| 		y = -12, height = 12, | ||||
| 		backgroundColor = colors.cyan, | ||||
| 		titleBar = UI.TitleBar { | ||||
| 			title = 'Edit Application', | ||||
| 			event = 'slide_hide', | ||||
| @@ -122,7 +126,7 @@ local page = UI.Page { | ||||
| 		form = UI.Form { | ||||
| 			y = 2, ey = -2, | ||||
| 			[1] = UI.TextEntry { | ||||
| 				formLabel = 'Title', formKey = 'title', limit = 11, help = 'Application title', | ||||
| 				formLabel = 'Title', formKey = 'title', limit = 11, width = 13, help = 'Application title', | ||||
| 				required = true, | ||||
| 			}, | ||||
| 			[2] = UI.TextEntry { | ||||
| @@ -130,21 +134,24 @@ local page = UI.Page { | ||||
| 				required = true, | ||||
| 			}, | ||||
| 			[3] = UI.TextEntry { | ||||
| 				formLabel = 'Category', formKey = 'category', limit = 11, help = 'Category of application', | ||||
| 				formLabel = 'Category', formKey = 'category', limit = 6, width = 8, help = 'Category of application', | ||||
| 				required = true, | ||||
| 			}, | ||||
| 			iconFile = UI.TextEntry { | ||||
| 				x = 11, ex = -12, y = 7, | ||||
| 				limit = 128, help = 'Path to icon file', | ||||
| 				shadowText = 'Path to icon file', | ||||
| 			editIcon = UI.Button { | ||||
| 				x = 11, y = 9, | ||||
| 				text = 'Edit', event = 'editIcon', help = 'Edit icon file', | ||||
| 			}, | ||||
| 			loadIcon = UI.Button { | ||||
| 				x = 11, y = 9, | ||||
| 				x = 18, y = 9, | ||||
| 				text = 'Load', event = 'loadIcon', help = 'Load icon file', | ||||
| 			}, | ||||
| 			info = UI.TextArea { | ||||
| 				x = 11, y = 6, height = 2, | ||||
| 				value = 'magenta is transparent\n3 high - max width is 8' | ||||
| 			}, | ||||
| 			image = UI.NftImage { | ||||
| 				backgroundColor = colors.black, | ||||
| 				y = 7, x = 2, height = 3, width = 8, | ||||
| 				y = 6, x = 2, height = 3, width = 8, | ||||
| 			}, | ||||
| 		}, | ||||
| 		notification = UI.Notification(), | ||||
| @@ -226,6 +233,7 @@ local function loadApplications() | ||||
| 	page:add { | ||||
| 		tabBar = UI.VerticalTabBar { | ||||
| 			buttons = buttons, | ||||
| 			selectedBackgroundColor = UI.colors.primary, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| @@ -308,14 +316,12 @@ function page.container:setCategory(categoryName, animate) | ||||
| 			image = UI.NftImage({ | ||||
| 				x = math.floor((width - icon.width) / 2) + 1, | ||||
| 				image = icon, | ||||
| 				width = 5, | ||||
| 				height = 3, | ||||
| 			}), | ||||
| 			button = UI.Button({ | ||||
| 				x = math.floor((width - #title - 2) / 2) + 1, | ||||
| 				y = 4, | ||||
| 				text = title, | ||||
| 				backgroundColor = self.backgroundColor, | ||||
| 				backgroundColor = self:getProperty('backgroundColor'), | ||||
| 				backgroundFocusColor = colors.gray, | ||||
| 				textColor = colors.white, | ||||
| 				textFocusColor = colors.white, | ||||
| @@ -333,7 +339,8 @@ function page.container:setCategory(categoryName, animate) | ||||
| 	local col, row = gutter, 2 | ||||
| 	local count = #self.children | ||||
|  | ||||
| 	local r = math.random(1, 5) | ||||
| 	local r = math.random(1, 7) | ||||
| 	local frames = 5 | ||||
| 	-- reposition all children | ||||
| 	for k,child in ipairs(self.children) do | ||||
| 		if r == 1 then | ||||
| @@ -355,14 +362,22 @@ function page.container:setCategory(categoryName, animate) | ||||
| 				child.x = self.width | ||||
| 				child.y = self.height - 3 | ||||
| 			end | ||||
| 		elseif r == 6 then | ||||
| 			child.x = col | ||||
| 			child.y = 1 | ||||
| 		elseif r == 7 then | ||||
| 			child.x = 1 | ||||
| 			child.y = self.height - 3 | ||||
| 		end | ||||
| 		child.tween = Tween.new(6, child, { x = col, y = row }, 'linear') | ||||
| 		child.tween = Tween.new(frames, child, { x = col, y = row }, 'inQuad') | ||||
|  | ||||
| 		if not animate then | ||||
| 			child.x = col | ||||
| 			child.y = row | ||||
| 		end | ||||
|  | ||||
| 		self:setViewHeight(row + 3) | ||||
|  | ||||
| 		if k < count then | ||||
| 			col = col + child.width | ||||
| 			if col + self.children[k + 1].width + gutter - 2 > self.width then | ||||
| @@ -377,15 +392,13 @@ function page.container:setCategory(categoryName, animate) | ||||
| 		local function transition() | ||||
| 			local i = 1 | ||||
| 			return function() | ||||
| 				self:clear() | ||||
| 				for _,child in pairs(self.children) do | ||||
| 					child.tween:update(1) | ||||
| 					child.x = math.floor(child.x) | ||||
| 					child.y = math.floor(child.y) | ||||
| 					child:draw() | ||||
| 					child:move(math.floor(child.x), math.floor(child.y)) | ||||
| 				end | ||||
| 				--os.sleep(.5) | ||||
| 				i = i + 1 | ||||
| 				return i < 7 | ||||
| 				return i <= frames | ||||
| 			end | ||||
| 		end | ||||
| 		self:addTransition(transition) | ||||
| @@ -512,17 +525,9 @@ function page.editor:updateApplications(app) | ||||
| 	loadApplications() | ||||
| end | ||||
|  | ||||
| function page.editor:eventHandler(event) | ||||
| 	if event.type == 'form_cancel' or event.type == 'cancel' then | ||||
| 		self:hide() | ||||
|  | ||||
| 	elseif event.type == 'focus_change' then | ||||
| 		self.statusBar:setStatus(event.focused.help or '') | ||||
| 		self.statusBar:draw() | ||||
|  | ||||
| 	elseif event.type == 'loadIcon' then | ||||
| function page.editor:loadImage(filename) | ||||
| 	local s, m = pcall(function() | ||||
| 			local iconLines = Util.readFile(self.form.iconFile.value) | ||||
| 		local iconLines = Util.readFile(filename) | ||||
| 		if not iconLines then | ||||
| 			error('Must be an NFT image - 3 rows, 8 cols max') | ||||
| 		end | ||||
| @@ -542,6 +547,31 @@ function page.editor:eventHandler(event) | ||||
| 		local msg = m:gsub('.*: (.*)', '%1') | ||||
| 		self.notification:error(msg) | ||||
| 	end | ||||
| end | ||||
|  | ||||
| function page.editor:eventHandler(event) | ||||
| 	if event.type == 'form_cancel' or event.type == 'cancel' then | ||||
| 		self:hide() | ||||
|  | ||||
| 	elseif event.type == 'focus_change' then | ||||
| 		self.statusBar:setStatus(event.focused.help or '') | ||||
| 		self.statusBar:draw() | ||||
|  | ||||
| 	elseif event.type == 'editIcon' then | ||||
| 		local filename = '/tmp/editing.nft' | ||||
| 		NFT.save(self.form.image.image or TRANS_ICON, filename) | ||||
| 		local success = shell.run('pain.lua ' .. filename) | ||||
| 		self.parent:dirty(true) | ||||
| 		if success then | ||||
| 			self:loadImage(filename) | ||||
| 		end | ||||
|  | ||||
| 	elseif event.type == 'loadIcon' then | ||||
| 		local success, filename = shell.run('fileui.lua') | ||||
| 		self.parent:dirty(true) | ||||
| 		if success and filename then | ||||
| 			self:loadImage(filename) | ||||
| 		end | ||||
|  | ||||
| 	elseif event.type == 'form_invalid' then | ||||
| 		self.notification:error(event.message) | ||||
| @@ -590,5 +620,4 @@ end) | ||||
| loadApplications() | ||||
|  | ||||
| UI:setPage(page) | ||||
|  | ||||
| UI:pullEvents() | ||||
| UI:start() | ||||
|   | ||||
| @@ -44,7 +44,6 @@ local page = UI.Page { | ||||
| 		marginRight = 0, marginLeft = 0, | ||||
| 	}, | ||||
| 	action = UI.SlideOut { | ||||
| 		backgroundColor = colors.cyan, | ||||
| 		titleBar = UI.TitleBar { | ||||
| 			event = 'hide-action', | ||||
| 		}, | ||||
|   | ||||
| @@ -48,6 +48,7 @@ local page = UI.Page { | ||||
| 			y = 2, | ||||
| 			filterTab = UI.Tab { | ||||
| 				tabTitle = 'Filter', | ||||
| 				noFill = true, | ||||
| 				filterGridText = UI.Text { | ||||
| 					x = 2, y = 2, | ||||
| 					value = 'ID filter', | ||||
| @@ -130,7 +131,6 @@ local page = UI.Page { | ||||
| 			title = 'Packet Information', | ||||
| 			event = 'packet_close', | ||||
| 		}, | ||||
| 		backgroundColor = colors.cyan, | ||||
| 		accelerators = { | ||||
| 			['backspace'] = 'packet_close', | ||||
| 			['left'] = 'prev_packet', | ||||
|   | ||||
| @@ -11,7 +11,7 @@ local systemPage = UI.Page { | ||||
| 		settings = UI.Tab { | ||||
| 			tabTitle = 'Category', | ||||
| 			grid = UI.ScrollingGrid { | ||||
| 				y = 2, | ||||
| 				x = 2, y = 2, ex = -2, ey = -2, | ||||
| 				columns = { | ||||
| 					{ heading = 'Name',        key = 'name'        }, | ||||
| 					{ heading = 'Description', key = 'description' }, | ||||
| @@ -35,7 +35,7 @@ function systemPage.tabs.settings:eventHandler(event) | ||||
| 			tab:disable() | ||||
| 		end | ||||
| 		systemPage.tabs:selectTab(tab) | ||||
| 		self.parent:draw() | ||||
| 		--self.parent:draw() | ||||
| 		return true | ||||
| 	end | ||||
| end | ||||
|   | ||||
| @@ -3,6 +3,7 @@ local UI    = require('opus.ui') | ||||
|  | ||||
| local kernel     = _G.kernel | ||||
| local multishell = _ENV.multishell | ||||
| local tasks      = multishell and multishell.getTabs and multishell.getTabs() or kernel.routines | ||||
|  | ||||
| UI:configure('Tasks', ...) | ||||
|  | ||||
| @@ -21,7 +22,7 @@ local page = UI.Page { | ||||
| 			{ heading = 'Status', key = 'status'    }, | ||||
| 			{ heading = 'Time',   key = 'timestamp' }, | ||||
| 		}, | ||||
| 		values = kernel.routines, | ||||
| 		values = tasks, | ||||
| 		sortColumn = 'uid', | ||||
| 		autospace = true, | ||||
| 		getDisplayValues = function (_, row) | ||||
|   | ||||
							
								
								
									
										138
									
								
								sys/apps/fileui.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								sys/apps/fileui.lua
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,138 @@ | ||||
| local UI   = require('opus.ui') | ||||
| local Util = require('opus.util') | ||||
|  | ||||
| local colors = _G.colors | ||||
| local fs     = _G.fs | ||||
| local shell  = _ENV.shell | ||||
|  | ||||
| local selected | ||||
|  | ||||
| -- fileui [--path=path] [--exec=filename] | ||||
|  | ||||
| local page = UI.Page { | ||||
| 	title = 'Select File', | ||||
| 	-- x = 3, ex = -3, y = 2, ey = -2, | ||||
| 	grid = UI.ScrollingGrid { | ||||
| 		x = 2, y = 2, ex = -2, ey = -4, | ||||
| 		path = '', | ||||
| 		sortColumn = 'name', | ||||
| 		columns = { | ||||
| 			{ heading = 'Name', key = 'name' }, | ||||
| 			{ heading = 'Size', key = 'size', width = 5 } | ||||
| 		}, | ||||
| 		getDisplayValues = function(_, row) | ||||
| 			if row.size then | ||||
| 				row = Util.shallowCopy(row) | ||||
| 				row.size = Util.toBytes(row.size) | ||||
| 			end | ||||
| 			return row | ||||
| 		end, | ||||
| 		getRowTextColor = function(_, file) | ||||
| 			if file.isDir then | ||||
| 				return colors.cyan | ||||
| 			end | ||||
| 			if file.isReadOnly then | ||||
| 				return colors.pink | ||||
| 			end | ||||
| 			return colors.white | ||||
| 		end, | ||||
| 		sortCompare = function(self, a, b) | ||||
| 			if self.sortColumn == 'size' then | ||||
| 				return a.size < b.size | ||||
| 			end | ||||
| 			if a.isDir == b.isDir then | ||||
| 				return a.name:lower() < b.name:lower() | ||||
| 			end | ||||
| 			return a.isDir | ||||
| 		end, | ||||
| 		draw = function(self) | ||||
| 			local files = fs.listEx(self.dir) | ||||
| 			if #self.dir > 0 then | ||||
| 				table.insert(files, { | ||||
| 					name = '..', | ||||
| 					isDir = true, | ||||
| 				}) | ||||
| 			end | ||||
| 			self:setValues(files) | ||||
| 			self:setIndex(1) | ||||
| 			UI.Grid.draw(self) | ||||
| 		end, | ||||
| 	}, | ||||
| 	path = UI.TextEntry { | ||||
| 		x  =  2, | ||||
| 		y  = -2, | ||||
| 		ex = -11, | ||||
| 		limit = 256, | ||||
| 		accelerators = { | ||||
| 			enter = 'path_enter', | ||||
| 		} | ||||
| 	}, | ||||
| 	cancel = UI.Button { | ||||
| 		text = 'Cancel', | ||||
| 		x = -9, | ||||
| 		y = -2, | ||||
| 		event = 'cancel', | ||||
| 	}, | ||||
| 	draw = function(self) | ||||
| 		self:fillArea(1, 1, self.width, self.height, string.rep('\127', self.width), colors.black, colors.gray) | ||||
| 		self:drawChildren() | ||||
| 	end, | ||||
| } | ||||
|  | ||||
| function page:enable(path) | ||||
| 	self:setPath(path or shell.dir()) | ||||
| 	UI.Page.enable(self) | ||||
| end | ||||
|  | ||||
| function page:setPath(path) | ||||
| 	self.grid.dir = path | ||||
| 	while not fs.isDir(self.grid.dir) do | ||||
| 		self.grid.dir = fs.getDir(self.grid.dir) | ||||
| 	end | ||||
|  | ||||
| 	self.path.value = self.grid.dir | ||||
| end | ||||
|  | ||||
| function page:eventHandler(event) | ||||
| 	if event.type == 'grid_select' then | ||||
| 		self.grid.dir = fs.combine(self.grid.dir, event.selected.name) | ||||
| 		self.path.value = self.grid.dir | ||||
| 		if event.selected.isDir then | ||||
| 			self.grid:draw() | ||||
| 			self.path:draw() | ||||
| 		else | ||||
| 			selected = self.path.value | ||||
| 			UI:quit() | ||||
| 		end | ||||
|  | ||||
| 	elseif event.type == 'path_enter' then | ||||
| 		if fs.isDir(self.path.value) then | ||||
| 			self:setPath(self.path.value) | ||||
| 			self.grid:draw() | ||||
| 			self.path:draw() | ||||
| 		else | ||||
| 			selected = self.path.value | ||||
| 			UI:quit() | ||||
| 		end | ||||
|  | ||||
| 	elseif event.type == 'cancel' then | ||||
| 		UI:quit() | ||||
| 	else | ||||
| 		return UI.Page.eventHandler(self, event) | ||||
| 	end | ||||
| 	return true | ||||
| end | ||||
|  | ||||
| local _, args = Util.parse(...) | ||||
|  | ||||
| UI:setPage(page, args.path) | ||||
| UI:start() | ||||
| UI.term:setCursorBlink(false) | ||||
|  | ||||
| if args.exec and selected then | ||||
| 	shell.openForegroundTab(string.format('%s %s', args.exec, selected)) | ||||
| 	return | ||||
| end | ||||
|  | ||||
| --print('selected: ' .. tostring(selected)) | ||||
| return selected | ||||
| @@ -1,5 +1,3 @@ | ||||
| _G.requireInjector(_ENV) | ||||
|  | ||||
| local Event = require('opus.event') | ||||
| local Util  = require('opus.util') | ||||
|  | ||||
|   | ||||
| @@ -66,11 +66,11 @@ local function run(env, ...) | ||||
| 		_ENV.multishell.setTitle(_ENV.multishell.getCurrent(), fs.getName(path):match('([^%.]+)')) | ||||
| 	end | ||||
|  | ||||
| 	if isUrl then | ||||
| 		tProgramStack[#tProgramStack + 1] = path -- path:match("^https?://([^/:]+:?[0-9]*/?.*)$") | ||||
| 	else | ||||
| 		tProgramStack[#tProgramStack + 1] = path | ||||
| 	end | ||||
| 	tProgramStack[#tProgramStack + 1] = { | ||||
| 		path = path, -- path:match("^https?://([^/:]+:?[0-9]*/?.*)$") | ||||
| 		env = env, | ||||
| 		args = args, | ||||
| 	} | ||||
|  | ||||
| 	env[ "arg" ] = { [0] = path, table.unpack(args) } | ||||
| 	local r = { fn(table.unpack(args)) } | ||||
| @@ -278,6 +278,10 @@ function shell.getCompletionInfo() | ||||
| end | ||||
|  | ||||
| function shell.getRunningProgram() | ||||
| 	return tProgramStack[#tProgramStack] and tProgramStack[#tProgramStack].path | ||||
| end | ||||
|  | ||||
| function shell.getRunningInfo() | ||||
| 	return tProgramStack[#tProgramStack] | ||||
| end | ||||
|  | ||||
|   | ||||
| @@ -20,7 +20,7 @@ local aliasTab = UI.Tab { | ||||
| 		}, | ||||
| 	}, | ||||
| 	grid = UI.Grid { | ||||
| 		y = 5, | ||||
| 		x = 2, y = 5, ex = -2, ey = -2, | ||||
| 		sortColumn = 'alias', | ||||
| 		columns = { | ||||
| 			{ heading = 'Alias',   key = 'alias' }, | ||||
|   | ||||
| @@ -22,20 +22,19 @@ local tab = UI.Tab { | ||||
| 		disableHeader = true, | ||||
| 		columns = { | ||||
| 			{ key = 'file' }, | ||||
| 		} | ||||
| 		}, | ||||
| 		getRowTextColor = function(self, row) | ||||
| 			if row == self.values[1] then | ||||
| 				return colors.yellow | ||||
| 			end | ||||
| 			return UI.Grid.getRowTextColor(self, row) | ||||
| 		end, | ||||
| 	}, | ||||
| 	statusBar = UI.StatusBar { | ||||
| 		values = 'Double-click to set as preferred' | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| function tab.choices:getRowTextColor(row) | ||||
| 	if row == self.values[1] then | ||||
| 		return colors.yellow | ||||
| 	end | ||||
| 	return UI.Grid.getRowTextColor(self, row) | ||||
| end | ||||
|  | ||||
| function tab:updateChoices() | ||||
| 	local app = self.apps:getSelected().name | ||||
| 	local choices = { } | ||||
|   | ||||
| @@ -4,16 +4,17 @@ local UI     = require('opus.ui') | ||||
|  | ||||
| local colors = _G.colors | ||||
|  | ||||
| -- -t80x30 | ||||
|  | ||||
| if _G.http.websocket then | ||||
| 	local config = Config.load('cloud') | ||||
|  | ||||
| 	local tab = UI.Tab { | ||||
| 		tabTitle = 'Cloud', | ||||
| 		description = 'Cloud Catcher options', | ||||
| 		[1] = UI.Window { | ||||
| 			x = 2, y = 2, ex = -2, ey = 4, | ||||
| 		}, | ||||
| 		key = UI.TextEntry { | ||||
| 			x = 3, ex = -3, y = 2, | ||||
| 			x = 3, ex = -3, y = 3, | ||||
| 			limit = 32, | ||||
| 			value = config.key, | ||||
| 			shadowText = 'Cloud key', | ||||
| @@ -22,13 +23,14 @@ if _G.http.websocket then | ||||
| 			}, | ||||
| 		}, | ||||
| 		button = UI.Button { | ||||
| 			x = 3, y = 4, | ||||
| 			text = 'Update', | ||||
| 			x = -8, ex = -2, y = -2, | ||||
| 			text = 'Apply', | ||||
| 			event = 'update_key', | ||||
| 		}, | ||||
| 		labelText = UI.TextArea { | ||||
| 			x = 3, ex = -3, y = 6, | ||||
| 			x = 2, ex = -2, y = 6, ey = -4, | ||||
| 			textColor = colors.yellow, | ||||
| 			backgroundColor = colors.black, | ||||
| 			marginLeft = 0, marginRight = 0, | ||||
| 			value = string.format( | ||||
| [[Use a non-changing cloud key. Note that only a single computer can use this session at one time. | ||||
|   | ||||
| @@ -20,8 +20,8 @@ local tab = UI.Tab { | ||||
| 	description = 'Visualise HDD and disks usage', | ||||
|  | ||||
| 	drives = UI.ScrollingGrid { | ||||
| 		x = 2, y = 1, | ||||
| 		ex = '47%', ey = -7, | ||||
| 		x = 2, y = 2, | ||||
| 		ex = '47%', ey = -8, | ||||
| 		columns = { | ||||
| 			{ heading = 'Drive', key = 'name' }, | ||||
| 			{ heading = 'Side' ,key = 'side', textColor = colors.yellow } | ||||
| @@ -30,7 +30,7 @@ local tab = UI.Tab { | ||||
| 	}, | ||||
| 	infos = UI.Grid { | ||||
| 		x = '52%', y = 2, | ||||
| 		ex = -2, ey = -4, | ||||
| 		ex = -2, ey = -8, | ||||
| 		disableHeader = true, | ||||
| 		unfocusedBackgroundSelectedColor = colors.black, | ||||
| 		inactive = true, | ||||
| @@ -40,18 +40,23 @@ local tab = UI.Tab { | ||||
| 			{ key = 'value', align = 'right', textColor = colors.yellow }, | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	[1] = UI.Window { | ||||
| 		x = 2, y = -6, ex = -2, ey = -2, | ||||
| 		backgroundColor = colors.black, | ||||
| 	}, | ||||
| 	progress = UI.ProgressBar { | ||||
| 		x = 11, y = -2, | ||||
| 		ex = -2, | ||||
| 		x = 11, y = -3, | ||||
| 		ex = -3, | ||||
| 	}, | ||||
| 	percentage = UI.Text { | ||||
| 		x = 11, y = -3, | ||||
| 		ex = '47%', | ||||
| 		align = 'center', | ||||
| 		y = -4, width = 5, | ||||
| 		x = 12, | ||||
| 		--align = 'center', | ||||
| 		backgroundColor = colors.black, | ||||
| 	}, | ||||
| 	icon = UI.NftImage { | ||||
| 		x = 2, y = -5, | ||||
| 		x = 2, y = -6, ey = -2, | ||||
| 		backgroundColor = colors.black, | ||||
| 		image = NFT.parse(NftImages.blank) | ||||
| 	}, | ||||
| } | ||||
|   | ||||
| @@ -8,7 +8,7 @@ local tab = UI.Tab { | ||||
| 	tabTitle = 'Kiosk', | ||||
| 	description = 'Kiosk options', | ||||
| 	form = UI.Form { | ||||
| 		x = 2, ex = -2, | ||||
| 		x = 2, y = 2, ex = -2, ey = 5, | ||||
| 		manualControls = true, | ||||
| 		monitor = UI.Chooser { | ||||
| 			formLabel = 'Monitor', formKey = 'monitor', | ||||
| @@ -22,11 +22,12 @@ local tab = UI.Tab { | ||||
| 			}, | ||||
| 			help = 'Adjust text scaling', | ||||
| 		}, | ||||
| 		labelText = UI.TextArea { | ||||
| 			x = 2, ex = -2, y = 5, | ||||
| 			textColor = colors.yellow, | ||||
| 			value = 'Settings apply to kiosk mode selected during startup' | ||||
| 	}, | ||||
| 	labelText = UI.TextArea { | ||||
| 		x = 2, ex = -2, y = 7, ey = -2, | ||||
| 		textColor = colors.yellow, | ||||
| 		backgroundColor = colors.black, | ||||
| 		value = 'Settings apply to kiosk mode selected during startup' | ||||
| 	}, | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -8,19 +8,22 @@ local labelTab = UI.Tab { | ||||
| 	tabTitle = 'Label', | ||||
| 	description = 'Set the computer label', | ||||
| 	labelText = UI.Text { | ||||
| 		x = 3, y = 2, | ||||
| 		x = 3, y = 3, | ||||
| 		value = 'Label' | ||||
| 	}, | ||||
| 	label = UI.TextEntry { | ||||
| 		x = 9, y = 2, ex = -4, | ||||
| 		x = 9, y = 3, ex = -4, | ||||
| 		limit = 32, | ||||
| 		value = os.getComputerLabel(), | ||||
| 		accelerators = { | ||||
| 			enter = 'update_label', | ||||
| 		}, | ||||
| 	}, | ||||
| 	[1] = UI.Window { | ||||
| 		x = 2, y = 2, ex = -2, ey = 4, | ||||
| 	}, | ||||
| 	grid = UI.ScrollingGrid { | ||||
| 		y = 3, | ||||
| 		x = 2, y = 6, ex = -2, ey = -2, | ||||
| 		values = { | ||||
| 			{ name = '',  value = ''                  }, | ||||
| 			{ name = 'CC version',  value = Util.getVersion()                  }, | ||||
| @@ -30,10 +33,11 @@ local labelTab = UI.Tab { | ||||
| 			{ name = 'Computer ID', value = tostring(os.getComputerID())       }, | ||||
| 			{ name = 'Day',         value = tostring(os.day())                 }, | ||||
| 		}, | ||||
| 		disableHeader = true, | ||||
| 		inactive = true, | ||||
| 		columns = { | ||||
| 			{ key = 'name',  width = 12 }, | ||||
| 			{ key = 'value' }, | ||||
| 			{ key = 'value', textColor = colors.yellow }, | ||||
| 		}, | ||||
| 	}, | ||||
| } | ||||
|   | ||||
| @@ -9,12 +9,15 @@ local config = Config.load('multishell') | ||||
| local tab = UI.Tab { | ||||
| 	tabTitle = 'Launcher', | ||||
| 	description = 'Set the application launcher', | ||||
| 	[1] = UI.Window { | ||||
| 		x = 2, y = 2, ex = -2, ey = 5, | ||||
| 	}, | ||||
| 	launcherLabel = UI.Text { | ||||
| 		x = 3, y = 2, | ||||
| 		x = 3, y = 3, | ||||
| 		value = 'Launcher', | ||||
| 	}, | ||||
| 	launcher = UI.Chooser { | ||||
| 		x = 13, y = 2, width = 12, | ||||
| 		x = 13, y = 3, width = 12, | ||||
| 		choices = { | ||||
| 			{ name = 'Overview', value = 'sys/apps/Overview.lua' }, | ||||
| 			{ name = 'Shell',    value = 'sys/apps/ShellLauncher.lua'    }, | ||||
| @@ -22,17 +25,18 @@ local tab = UI.Tab { | ||||
| 		}, | ||||
| 	}, | ||||
| 	custom = UI.TextEntry { | ||||
| 		x = 13, ex = -3, y = 3, | ||||
| 		x = 13, ex = -3, y = 4, | ||||
| 		limit = 128, | ||||
| 		shadowText = 'File name', | ||||
| 	}, | ||||
| 	button = UI.Button { | ||||
| 		x = 3, y = 5, | ||||
| 		text = 'Update', | ||||
| 		x = -8, ex = -2, y = -2, | ||||
| 		text = 'Apply', | ||||
| 		event = 'update', | ||||
| 	}, | ||||
| 	labelText = UI.TextArea { | ||||
| 		x = 3, ex = -3, y = 7, | ||||
| 		x = 2, ex = -2, y = 7, ey = -4, | ||||
| 		backgroundColor = colors.black, | ||||
| 		textColor = colors.yellow, | ||||
| 		value = 'Choose an application launcher', | ||||
| 	}, | ||||
|   | ||||
| @@ -2,24 +2,29 @@ local Ansi   = require('opus.ansi') | ||||
| local Config = require('opus.config') | ||||
| local UI     = require('opus.ui') | ||||
|  | ||||
| local colors = _G.colors | ||||
| local device = _G.device | ||||
|  | ||||
| local tab = UI.Tab { | ||||
| 	tabTitle = 'Network', | ||||
| 	description = 'Networking options', | ||||
| 	info = UI.TextArea { | ||||
| 		x = 3, y = 4, | ||||
| 		x = 2, y = 6, ex = -2, ey = -2, | ||||
| 		backgroundColor = colors.black, | ||||
| 		value = string.format( | ||||
| [[%sSet the primary modem used for wireless communications.%s | ||||
|  | ||||
| Reboot to take effect.]], Ansi.yellow, Ansi.reset) | ||||
| 	}, | ||||
| 	[1] = UI.Window { | ||||
| 		x = 2, y = 2, ex = -2, ey = 4, | ||||
| 	}, | ||||
| 	label = UI.Text { | ||||
| 		x = 3, y = 2, | ||||
| 		x = 3, y = 3, | ||||
| 		value = 'Modem', | ||||
| 	}, | ||||
| 	modem = UI.Chooser { | ||||
| 		x = 10, ex = -3, y = 2, | ||||
| 		x = 10, ex = -3, y = 3, | ||||
| 		nochoice = 'auto', | ||||
| 	}, | ||||
| } | ||||
|   | ||||
| @@ -7,6 +7,9 @@ local colors   = _G.colors | ||||
| local passwordTab = UI.Tab { | ||||
| 	tabTitle = 'Password', | ||||
| 	description = 'Wireless network password', | ||||
| 	[1] = UI.Window { | ||||
| 		x = 2, y = 2, ex = -2, ey = 4, | ||||
| 	}, | ||||
| 	newPass = UI.TextEntry { | ||||
| 		x = 3, ex = -3, y = 3, | ||||
| 		limit = 32, | ||||
| @@ -17,12 +20,13 @@ local passwordTab = UI.Tab { | ||||
| 		}, | ||||
| 	}, | ||||
| 	button = UI.Button { | ||||
| 		x = 3, y = 5, | ||||
| 		text = 'Update', | ||||
| 		x = -8, ex = -2, y = -2, | ||||
| 		text = 'Apply', | ||||
| 		event = 'update_password', | ||||
| 	}, | ||||
| 	info = UI.TextArea { | ||||
| 		x = 3, ex = -3, y = 7, | ||||
| 		x = 2, ex = -2, y = 6, ey = -4, | ||||
| 		backgroundColor = colors.black, | ||||
| 		textColor = colors.yellow, | ||||
| 		inactive = true, | ||||
| 		value = 'Add a password to enable other computers to connect to this one.', | ||||
|   | ||||
| @@ -6,8 +6,11 @@ local tab = UI.Tab { | ||||
| 	tabTitle = 'Path', | ||||
| 	description = 'Set the shell path', | ||||
| 	tabClose = true, | ||||
| 	[1] = UI.Window { | ||||
| 		x = 2, y = 2, ex = -2, ey = 4, | ||||
| 	}, | ||||
| 	entry = UI.TextEntry { | ||||
| 		x = 2, y = 2, ex = -2, | ||||
| 		x = 3, y = 3, ex = -3, | ||||
| 		limit = 256, | ||||
| 		shadowText = 'enter new path', | ||||
| 		accelerators = { | ||||
| @@ -16,7 +19,7 @@ local tab = UI.Tab { | ||||
| 		help = 'add a new path', | ||||
| 	}, | ||||
| 	grid = UI.Grid { | ||||
| 		y = 4, ey = -3, | ||||
| 		x = 2, y = 6, ex = -2, ey = -3, | ||||
| 		disableHeader = true, | ||||
| 		columns = { { key = 'value' } }, | ||||
| 		autospace = true, | ||||
|   | ||||
| @@ -7,7 +7,7 @@ if settings then | ||||
| 		tabTitle = 'Settings', | ||||
| 		description = 'Computercraft configurable settings', | ||||
| 		grid = UI.Grid { | ||||
| 			y = 2, | ||||
| 			x = 2, y = 2, ex = -2, ey = -2, | ||||
| 			autospace = true, | ||||
| 			sortColumn = 'name', | ||||
| 			columns = { | ||||
|   | ||||
| @@ -42,25 +42,23 @@ local tab = UI.Tab { | ||||
| 	tabTitle = 'Shell', | ||||
| 	description = 'Shell options', | ||||
| 	grid1 = UI.ScrollingGrid { | ||||
| 		y = 2, ey = -10, x = 3, ex = -16, | ||||
| 		y = 2, ey = -10, x = 2, ex = -17, | ||||
| 		disableHeader = true, | ||||
| 		columns = { { key = 'name' } }, | ||||
| 		values = allSettings, | ||||
| 		sortColumn = 'name', | ||||
| 	}, | ||||
| 	grid2 = UI.ScrollingGrid { | ||||
| 		y = 2, ey = -10, x = -14, ex = -3, | ||||
| 		y = 2, ey = -10, x = -14, ex = -2, | ||||
| 		disableHeader = true, | ||||
| 		columns = { { key = 'name' } }, | ||||
| 		values = allColors, | ||||
| 		sortColumn = 'name', | ||||
| 	}, | ||||
| 	directoryLabel = UI.Text { | ||||
| 		x = 2, y = -2, | ||||
| 		value = 'Display directory', | ||||
| 	}, | ||||
| 	directory = UI.Checkbox { | ||||
| 		x = 20, y = -2, | ||||
| 		x = 2, y = -2, | ||||
| 		labelBackgroundColor = colors.black, | ||||
| 		label = 'Directory', | ||||
| 		value = config.displayDirectory | ||||
| 	}, | ||||
| 	reset = UI.Button { | ||||
| @@ -74,7 +72,7 @@ local tab = UI.Tab { | ||||
| 		event = 'update', | ||||
| 	}, | ||||
| 	display = UI.Window { | ||||
| 		x = 3, ex = -3, y = -8, height = 5, | ||||
| 		x = 2, ex = -2, y = -8, height = 5, | ||||
| 	}, | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -21,4 +21,4 @@ deleteIfExists('sys/autorun/apps.lua') | ||||
| deleteIfExists('sys/init/6.tl3.lua') | ||||
|  | ||||
| -- remove this file | ||||
| deleteIfExists('sys/autorun/upgraded.lua') | ||||
| --deleteIfExists('sys/autorun/upgraded.lua') | ||||
| @@ -1,5 +1,3 @@ | ||||
| _G.requireInjector(_ENV) | ||||
|  | ||||
| local Peripheral = require('opus.peripheral') | ||||
|  | ||||
| _G.device = Peripheral.getList() | ||||
|   | ||||
| @@ -4,11 +4,8 @@ if fs.native then | ||||
| 	return | ||||
| end | ||||
|  | ||||
| _G.requireInjector(_ENV) | ||||
| local Util = require('opus.util') | ||||
|  | ||||
| -- TODO: support getDrive for virtual nodes | ||||
|  | ||||
| fs.native = Util.shallowCopy(fs) | ||||
|  | ||||
| local fstypes = { } | ||||
| @@ -23,7 +20,6 @@ for k,fn in pairs(fs) do | ||||
| end | ||||
|  | ||||
| function nativefs.list(node, dir) | ||||
|  | ||||
| 	local files | ||||
| 	if fs.native.isDir(dir) then | ||||
| 		files = fs.native.list(dir) | ||||
| @@ -265,7 +261,6 @@ local function getfstype(fstype) | ||||
| end | ||||
|  | ||||
| function fs.mount(path, fstype, ...) | ||||
|  | ||||
| 	local vfs = getfstype(fstype) | ||||
| 	if not vfs then | ||||
| 		error('Invalid file system type') | ||||
|   | ||||
| @@ -1,5 +1,3 @@ | ||||
| _G.requireInjector(_ENV) | ||||
|  | ||||
| local Config = require('opus.config') | ||||
|  | ||||
| local device     = _G.device | ||||
|   | ||||
| @@ -1,5 +1,3 @@ | ||||
| _G.requireInjector(_ENV) | ||||
|  | ||||
| local Config   = require('opus.config') | ||||
| local trace    = require('opus.trace') | ||||
| local Util     = require('opus.util') | ||||
| @@ -334,16 +332,12 @@ kernel.hook('mouse_scroll', function(_, eventData) | ||||
| end) | ||||
|  | ||||
| kernel.hook('kernel_ready', function() | ||||
| 	local env = Util.shallowCopy(shell.getEnv()) | ||||
| 	_G.requireInjector(env) | ||||
|  | ||||
| 	overviewId = multishell.openTab({ | ||||
| 		path = config.launcher or 'sys/apps/Overview.lua', | ||||
| 		isOverview = true, | ||||
| 		noTerminate = true, | ||||
| 		focused = true, | ||||
| 		title = '+', | ||||
| 		env = env, | ||||
| 	}) | ||||
|  | ||||
| 	multishell.openTab({ | ||||
|   | ||||
| @@ -1,5 +1,3 @@ | ||||
| _G.requireInjector(_ENV) | ||||
|  | ||||
| local Array    = require('opus.array') | ||||
| local Terminal = require('opus.terminal') | ||||
| local Util     = require('opus.util') | ||||
|   | ||||
| @@ -14,7 +14,7 @@ function Array.removeByValue(t, e) | ||||
| 	for k,v in pairs(t) do | ||||
| 		if v == e then | ||||
| 			table.remove(t, k) | ||||
| 			break | ||||
| 			return e | ||||
| 		end | ||||
| 	end | ||||
| end | ||||
|   | ||||
| @@ -50,6 +50,7 @@ function input:toCode(ch, code) | ||||
| 	   table.insert(result, 'alt') | ||||
| 	end | ||||
|  | ||||
| 	if ch then -- some weird things happen with control/command on mac | ||||
| 		if keyboard.state[keys.leftShift] or keyboard.state[keys.rightShift] or | ||||
| 			code == keys.leftShift or code == keys.rightShift then | ||||
| 			if code and modifiers[code] then | ||||
| @@ -63,6 +64,7 @@ function input:toCode(ch, code) | ||||
| 		elseif not code or not modifiers[code] then | ||||
| 			table.insert(result, ch) | ||||
| 		end | ||||
| 	end | ||||
|  | ||||
| 	return table.concat(result, '-') | ||||
| end | ||||
| @@ -118,6 +120,7 @@ function input:translate(event, code, p1, p2) | ||||
| 		local buttons = { 'mouse_click', 'mouse_rightclick' } | ||||
| 		self.mch = buttons[code] | ||||
| 		self.mfired = nil | ||||
| 		self.anchor = { x = p1, y = p2 } | ||||
| 		return { | ||||
| 			code = input:toCode('mouse_down', 255), | ||||
| 			button = code, | ||||
| @@ -132,6 +135,8 @@ function input:translate(event, code, p1, p2) | ||||
| 			button = code, | ||||
| 			x = p1, | ||||
| 			y = p2, | ||||
| 			dx = p1 - self.anchor.x, | ||||
| 			dy = p2 - self.anchor.y, | ||||
| 		} | ||||
|  | ||||
| 	elseif event == 'mouse_up' then | ||||
|   | ||||
| @@ -1,16 +1,19 @@ | ||||
| local Util = require('opus.util') | ||||
|  | ||||
| local colors = _G.colors | ||||
|  | ||||
| local NFT = { } | ||||
|  | ||||
| -- largely copied from http://www.computercraft.info/forums2/index.php?/topic/5029-145-npaintpro/ | ||||
|  | ||||
| local tColourLookup = { } | ||||
| local hexToColor = { } | ||||
| for n = 1, 16 do | ||||
| 	tColourLookup[string.byte("0123456789abcdef", n, n)] = 2 ^ (n - 1) | ||||
| 	hexToColor[string.sub("0123456789abcdef", n, n)] = 2 ^ (n - 1) | ||||
| end | ||||
| local colorToHex = Util.transpose(hexToColor) | ||||
|  | ||||
| local function getColourOf(hex) | ||||
| 	return tColourLookup[hex:byte()] | ||||
| 	return hexToColor[hex] | ||||
| end | ||||
|  | ||||
| function NFT.parse(imageText) | ||||
| @@ -62,8 +65,22 @@ function NFT.parse(imageText) | ||||
| 	return image | ||||
| end | ||||
|  | ||||
| function NFT.load(path) | ||||
| function NFT.transparency(image) | ||||
| 	for y = 1, image.height do | ||||
| 		for _,key in pairs(Util.keys(image.fg[y])) do | ||||
| 			if image.fg[y][key] == colors.magenta then | ||||
| 				image.fg[y][key] = nil | ||||
| 			end | ||||
| 		end | ||||
| 		for _,key in pairs(Util.keys(image.bg[y])) do | ||||
| 			if image.bg[y][key] == colors.magenta then | ||||
| 				image.bg[y][key] = nil | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
| end | ||||
|  | ||||
| function NFT.load(path) | ||||
| 	local imageText = Util.readFile(path) | ||||
| 	if not imageText then | ||||
| 		error('Unable to read image file') | ||||
| @@ -71,4 +88,35 @@ function NFT.load(path) | ||||
| 	return NFT.parse(imageText) | ||||
| end | ||||
|  | ||||
| function NFT.save(image, filename) | ||||
| 	local bgcode, txcode = '\30', '\31' | ||||
| 	local output = { } | ||||
|  | ||||
| 	for y = 1, image.height do | ||||
| 		local lastBG, lastFG | ||||
| 		if image.text[y] then | ||||
| 			for x = 1, #image.text[y] do | ||||
| 				local bg = image.bg[y][x] or colors.magenta | ||||
| 				if bg ~= lastBG then | ||||
| 					lastBG = bg | ||||
| 					table.insert(output, bgcode .. colorToHex[bg]) | ||||
| 				end | ||||
|  | ||||
| 				local fg = image.fg[y][x] or colors.magenta | ||||
| 				if fg ~= lastFG then | ||||
| 					lastFG = fg | ||||
| 					table.insert(output, txcode .. colorToHex[fg]) | ||||
| 				end | ||||
|  | ||||
| 				table.insert(output, image.text[y][x]) | ||||
| 			end | ||||
| 		end | ||||
|  | ||||
| 		if y < image.height then | ||||
| 			table.insert(output, '\n') | ||||
| 		end | ||||
| 	end | ||||
| 	Util.writeFile(filename, table.concat(output)) | ||||
| end | ||||
|  | ||||
| return NFT | ||||
|   | ||||
| @@ -38,7 +38,7 @@ function Terminal.window(parent, sx, sy, w, h, isVisible) | ||||
| 	local blink = false | ||||
| 	local bg, fg = parent.getBackgroundColor(), parent.getTextColor() | ||||
|  | ||||
| 	local canvas = Canvas({ | ||||
| 	win.canvas = Canvas({ | ||||
| 		x       = sx, | ||||
| 		y       = sy, | ||||
| 		width   = w, | ||||
| @@ -47,50 +47,53 @@ function Terminal.window(parent, sx, sy, w, h, isVisible) | ||||
| 		offy    = 0, | ||||
| 	}) | ||||
|  | ||||
| 	win.canvas = canvas | ||||
|  | ||||
| 	local function update() | ||||
| 		if isVisible then | ||||
| 			canvas:render(parent) | ||||
| 			win.canvas:render(parent) | ||||
| 			win.setCursorPos(cx, cy) | ||||
| 		end | ||||
| 	end | ||||
|  | ||||
| 	local function scrollTo(y) | ||||
| 		y = math.max(0, y) | ||||
| 		y = math.min(#canvas.lines - canvas.height, y) | ||||
| 		y = math.min(#win.canvas.lines - win.canvas.height, y) | ||||
|  | ||||
| 		if y ~= canvas.offy then | ||||
| 			canvas.offy = y | ||||
| 			canvas:dirty() | ||||
| 		if y ~= win.canvas.offy then | ||||
| 			win.canvas.offy = y | ||||
| 			win.canvas:dirty() | ||||
| 			update() | ||||
| 		end | ||||
| 	end | ||||
|  | ||||
| 	function win.write(str) | ||||
| 		str = tostring(str) or '' | ||||
| 		canvas:write(cx, cy + canvas.offy, str, bg, fg) | ||||
| 		win.canvas:write(cx, cy +  win.canvas.offy, str, bg, fg) | ||||
| 		win.setCursorPos(cx + #str, cy) | ||||
| 		update() | ||||
| 	end | ||||
|  | ||||
| 	function win.blit(str, fg, bg) | ||||
| 		canvas:blit(cx, cy + canvas.offy, str, bg, fg) | ||||
| 		win.canvas:blit(cx, cy + win.canvas.offy, str, bg, fg) | ||||
| 		win.setCursorPos(cx + #str, cy) | ||||
| 		update() | ||||
| 	end | ||||
|  | ||||
| 	function win.clear() | ||||
| 		canvas.offy = 0 | ||||
| 		for i = #canvas.lines, canvas.height + 1, -1 do | ||||
| 			canvas.lines[i] = nil | ||||
| 		win.canvas.offy = 0 | ||||
| 		for i = #win.canvas.lines, win.canvas.height + 1, -1 do | ||||
| 			win.canvas.lines[i] = nil | ||||
| 		end | ||||
| 		canvas:clear(bg, fg) | ||||
| 		win.canvas:clear(bg, fg) | ||||
| 		update() | ||||
| 	end | ||||
|  | ||||
| 	function win.getLine(n) | ||||
| 		local line = win.canvas.lines[n] | ||||
| 		return line.text, line.fg, line.bg | ||||
| 	end | ||||
|  | ||||
| 	function win.clearLine() | ||||
| 		canvas:clearLine(cy + canvas.offy, bg, fg) | ||||
| 		win.canvas:clearLine(cy + win.canvas.offy, bg, fg) | ||||
| 		win.setCursorPos(cx, cy) | ||||
| 		update() | ||||
| 	end | ||||
| @@ -102,10 +105,14 @@ function Terminal.window(parent, sx, sy, w, h, isVisible) | ||||
| 	function win.setCursorPos(x, y) | ||||
| 		cx, cy = math.floor(x), math.floor(y) | ||||
| 		if isVisible then | ||||
| 			parent.setCursorPos(cx + canvas.x - 1, cy + canvas.y - 1) | ||||
| 			parent.setCursorPos(cx + win.canvas.x - 1, cy + win.canvas.y - 1) | ||||
| 		end | ||||
| 	end | ||||
|  | ||||
| 	function win.getCursorBlink() | ||||
| 		return blink | ||||
| 	end | ||||
|  | ||||
| 	function win.setCursorBlink(b) | ||||
| 		blink = b | ||||
| 		if isVisible then | ||||
| @@ -114,7 +121,7 @@ function Terminal.window(parent, sx, sy, w, h, isVisible) | ||||
| 	end | ||||
|  | ||||
| 	function win.isColor() | ||||
| 		return canvas.isColor | ||||
| 		return win.canvas.isColor | ||||
| 	end | ||||
| 	win.isColour = win.isColor | ||||
|  | ||||
| @@ -144,22 +151,22 @@ function Terminal.window(parent, sx, sy, w, h, isVisible) | ||||
| 	win.setBackgroundColour = win.setBackgroundColor | ||||
|  | ||||
| 	function win.getSize() | ||||
| 		return canvas.width, canvas.height | ||||
| 		return win.canvas.width, win.canvas.height | ||||
| 	end | ||||
|  | ||||
| 	function win.scroll(n) | ||||
| 		n = n or 1 | ||||
| 		if n > 0 then | ||||
| 			local lines = #canvas.lines | ||||
| 			local lines = #win.canvas.lines | ||||
| 			for i = 1, n do | ||||
| 				canvas.lines[lines + i] = { } | ||||
| 				canvas:clearLine(lines + i, bg, fg) | ||||
| 				win.canvas.lines[lines + i] = { } | ||||
| 				win.canvas:clearLine(lines + i, bg, fg) | ||||
| 			end | ||||
| 			while #canvas.lines > maxScroll do | ||||
| 				table.remove(canvas.lines, 1) | ||||
| 			while #win.canvas.lines > maxScroll do | ||||
| 				table.remove(win.canvas.lines, 1) | ||||
| 			end | ||||
| 			scrollTo(#canvas.lines) | ||||
| 			canvas:dirty() | ||||
| 			scrollTo(#win.canvas.lines) | ||||
| 			win.canvas:dirty() | ||||
| 			update() | ||||
| 		end | ||||
| 	end | ||||
| @@ -178,7 +185,7 @@ function Terminal.window(parent, sx, sy, w, h, isVisible) | ||||
| 		if visible ~= isVisible then | ||||
| 			isVisible = visible | ||||
| 			if isVisible then | ||||
| 				canvas:dirty() | ||||
| 				win.canvas:dirty() | ||||
| 				update() | ||||
| 			end | ||||
| 		end | ||||
| @@ -186,7 +193,7 @@ function Terminal.window(parent, sx, sy, w, h, isVisible) | ||||
|  | ||||
| 	function win.redraw() | ||||
| 		if isVisible then | ||||
| 			canvas:dirty() | ||||
| 			win.canvas:dirty() | ||||
| 			update() | ||||
| 		end | ||||
| 	end | ||||
| @@ -200,21 +207,21 @@ function Terminal.window(parent, sx, sy, w, h, isVisible) | ||||
| 	end | ||||
|  | ||||
| 	function win.getPosition() | ||||
| 		return canvas.x, canvas.y | ||||
| 		return win.canvas.x, win.canvas.y | ||||
| 	end | ||||
|  | ||||
| 	function win.reposition(x, y, width, height) | ||||
| 		canvas.x, canvas.y = x, y | ||||
| 		canvas:resize(width or canvas.width, height or canvas.height) | ||||
| 		win.canvas.x, win.canvas.y = x, y | ||||
| 		win.canvas:resize(width or win.canvas.width, height or win.canvas.height) | ||||
| 	end | ||||
|  | ||||
| 	--[[ Additional methods ]]-- | ||||
| 	function win.scrollDown() | ||||
| 		scrollTo(canvas.offy + 1) | ||||
| 		scrollTo(win.canvas.offy + 1) | ||||
| 	end | ||||
|  | ||||
| 	function win.scrollUp() | ||||
| 		scrollTo(canvas.offy - 1) | ||||
| 		scrollTo(win.canvas.offy - 1) | ||||
| 	end | ||||
|  | ||||
| 	function win.scrollTop() | ||||
| @@ -222,7 +229,7 @@ function Terminal.window(parent, sx, sy, w, h, isVisible) | ||||
| 	end | ||||
|  | ||||
| 	function win.scrollBottom() | ||||
| 		scrollTo(#canvas.lines) | ||||
| 		scrollTo(#win.canvas.lines) | ||||
| 	end | ||||
|  | ||||
| 	function win.setMaxScroll(ms) | ||||
| @@ -230,37 +237,35 @@ function Terminal.window(parent, sx, sy, w, h, isVisible) | ||||
| 	end | ||||
|  | ||||
| 	function win.getCanvas() | ||||
| 		return canvas | ||||
| 		return win.canvas | ||||
| 	end | ||||
|  | ||||
| 	function win.getParent() | ||||
| 		return parent | ||||
| 	end | ||||
|  | ||||
| 	canvas:clear() | ||||
| 	win.canvas:clear() | ||||
|  | ||||
| 	return win | ||||
| end | ||||
|  | ||||
| -- get windows contents | ||||
| function Terminal.getContents(win, parent) | ||||
| 	local oblit, oscp = parent.blit, parent.setCursorPos | ||||
| 	local lines = { } | ||||
| function Terminal.getContents(win) | ||||
| 	if not win.getLine then | ||||
| 		error('window is required') | ||||
| 	end | ||||
|  | ||||
| 	parent.blit = function(text, fg, bg) | ||||
| 		lines[#lines + 1] = { | ||||
| 	local lines = { } | ||||
| 	local _, h = win.getSize() | ||||
|  | ||||
| 	for i = 1, h do | ||||
| 		local text, fg, bg = win.getLine(i) | ||||
| 		lines[i] = { | ||||
| 			text = text, | ||||
| 			fg = fg, | ||||
| 			bg = bg, | ||||
| 		} | ||||
| 	end | ||||
| 	parent.setCursorPos = function() end | ||||
|  | ||||
| 	win.setVisible(true) | ||||
| 	win.redraw() | ||||
|  | ||||
| 	parent.blit = oblit | ||||
| 	parent.setCursorPos = oscp | ||||
|  | ||||
| 	return lines | ||||
| end | ||||
|   | ||||
| @@ -1,8 +1,10 @@ | ||||
| local Array      = require('opus.array') | ||||
| local class      = require('opus.class') | ||||
| local Event      = require('opus.event') | ||||
| local Input      = require('opus.input') | ||||
| local Transition = require('opus.ui.transition') | ||||
| local Util       = require('opus.util') | ||||
| local Canvas     = require('opus.ui.canvas') | ||||
|  | ||||
| local _rep       = string.rep | ||||
| local _sub       = string.sub | ||||
| @@ -44,8 +46,7 @@ function Manager:init() | ||||
| 		local currentPage = self:getActivePage() | ||||
| 		if ie and currentPage then | ||||
| 			local target = currentPage.focused or currentPage | ||||
| 			self:inputEvent(target, | ||||
| 				{ type = 'key', key = ie.code == 'char' and ie.ch or ie.code, element = target, ie = ie }) | ||||
| 			target:emit({ type = 'key', key = ie.code == 'char' and ie.ch or ie.code, element = target, ie = ie }) | ||||
| 			currentPage:sync() | ||||
| 		end | ||||
| 	end | ||||
| @@ -81,8 +82,7 @@ function Manager:init() | ||||
| 				} | ||||
| 				-- revisit - should send out scroll_up and scroll_down events | ||||
| 				-- let the element convert them to up / down | ||||
| 				self:inputEvent(event.element, | ||||
| 					{ type = 'key', key = directions[direction] }) | ||||
| 				event.element:emit({ type = 'key', key = directions[direction] }) | ||||
| 				currentPage:sync() | ||||
| 			end | ||||
| 		end, | ||||
| @@ -92,7 +92,7 @@ function Manager:init() | ||||
| 			if dev and dev.currentPage then | ||||
| 				Input:translate('mouse_click', 1, x, y) | ||||
| 				local ie = Input:translate('mouse_up', 1, x, y) | ||||
| 				self:click(dev.currentPage, ie.code, 1, x, y) | ||||
| 				self:click(dev.currentPage, ie) | ||||
| 			end | ||||
| 		end, | ||||
|  | ||||
| @@ -107,7 +107,7 @@ function Manager:init() | ||||
| 						currentPage:setFocus(event.element) | ||||
| 						currentPage:sync() | ||||
| 					end | ||||
| 					self:click(currentPage, ie.code, button, x, y) | ||||
| 					self:click(currentPage, ie) | ||||
| 				end | ||||
| 			end | ||||
| 		end, | ||||
| @@ -125,7 +125,7 @@ function Manager:init() | ||||
|  | ||||
| 			elseif ie and currentPage then | ||||
| 				if not currentPage.parent.device.side then | ||||
| 					self:click(currentPage, ie.code, button, x, y) | ||||
| 					self:click(currentPage, ie) | ||||
| 				end | ||||
| 			end | ||||
| 		end, | ||||
| @@ -135,7 +135,7 @@ function Manager:init() | ||||
| 			local currentPage = self:getActivePage() | ||||
|  | ||||
| 			if ie and currentPage then | ||||
| 				self:click(currentPage, ie.code, button, x, y) | ||||
| 				self:click(currentPage, ie) | ||||
| 			end | ||||
| 		end, | ||||
|  | ||||
| @@ -247,30 +247,44 @@ function Manager:emitEvent(event) | ||||
| 	end | ||||
| end | ||||
|  | ||||
| function Manager:inputEvent(parent, event) -- deprecate ? | ||||
| 	return parent and parent:emit(event) | ||||
| function Manager:click(target, ie) | ||||
| 	local clickEvent | ||||
|  | ||||
| 	if ie.code == 'mouse_drag' then | ||||
| 		clickEvent = { | ||||
| 			element = self.lastClicked, | ||||
| 			x = ie.x, | ||||
| 			y = ie.y,   -- this is not correct (should be relative to element) | ||||
| 			dx = ie.dx, | ||||
| 			dy = ie.dy, | ||||
| 		} | ||||
| 	else | ||||
| 		clickEvent = target:pointToChild(ie.x, ie.y) | ||||
| 	end | ||||
|  | ||||
| function Manager:click(target, code, button, x, y) | ||||
| 	local clickEvent = target:pointToChild(x, y) | ||||
| 	-- hack for dropdown menus | ||||
| 	if ie.code == 'mouse_click' and not clickEvent.element.focus then | ||||
| 		self:emitEvent({ type = 'mouse_out' }) | ||||
| 	end | ||||
|  | ||||
| 	if code == 'mouse_doubleclick' then | ||||
| 		if self.doubleClickElement ~= clickEvent.element then | ||||
| 	if ie.code == 'mouse_doubleclick' then | ||||
| 		if self.lastClicked ~= clickEvent.element then | ||||
| 			return | ||||
| 		end | ||||
| 	else | ||||
| 		self.doubleClickElement = clickEvent.element | ||||
| 		self.lastClicked = clickEvent.element | ||||
| 	end | ||||
|  | ||||
| 	clickEvent.button = button | ||||
| 	clickEvent.type = code | ||||
| 	clickEvent.key = code | ||||
| 	clickEvent.ie = { code = code, x = clickEvent.x, y = clickEvent.y } | ||||
| 	clickEvent.button = ie.button | ||||
| 	clickEvent.type = ie.code | ||||
| 	clickEvent.key = ie.code | ||||
| 	clickEvent.ie = { code = ie.code, x = clickEvent.x, y = clickEvent.y } | ||||
| 	clickEvent.raw = ie | ||||
|  | ||||
| 	if clickEvent.element.focus then | ||||
| 		target:setFocus(clickEvent.element) | ||||
| 	end | ||||
| 	self:inputEvent(clickEvent.element, clickEvent) | ||||
| 	clickEvent.element:emit(clickEvent) | ||||
|  | ||||
| 	target:sync() | ||||
| end | ||||
| @@ -310,7 +324,6 @@ end | ||||
|  | ||||
| function Manager:setActivePage(page) | ||||
| 	page.parent.currentPage = page | ||||
| 	page.parent.canvas = page.canvas | ||||
| end | ||||
|  | ||||
| function Manager:setPage(pageOrName, ...) | ||||
| @@ -388,6 +401,12 @@ function Manager:pullEvents(...) | ||||
| 	end | ||||
| end | ||||
|  | ||||
| Manager.colors = { | ||||
| 	primary = colors.cyan, | ||||
| 	secondary = colors.blue, | ||||
| 	tertiary = colors.blue, | ||||
| } | ||||
|  | ||||
| Manager.exitPullEvents = Event.exitPullEvents | ||||
| Manager.quit = Event.exitPullEvents | ||||
| Manager.start = Manager.pullEvents | ||||
| @@ -395,7 +414,7 @@ Manager.start = Manager.pullEvents | ||||
| local UI = Manager() | ||||
|  | ||||
| --[[-- Basic drawable area --]]-- | ||||
| UI.Window = class() | ||||
| UI.Window = class(Canvas) | ||||
| UI.Window.uid = 1 | ||||
| UI.Window.docs = { } | ||||
| UI.Window.defaults = { | ||||
| @@ -413,7 +432,9 @@ function UI.Window:init(args) | ||||
| 	local defaults = args | ||||
| 	local m = getmetatable(self)  -- get the class for this instance | ||||
| 	repeat | ||||
| 		if m.disable then | ||||
| 			defaults = UI:getDefaults(m, defaults) | ||||
| 		end | ||||
| 		m = m._base | ||||
| 	until not m | ||||
| 	UI:mergeProperties(self, defaults) | ||||
| @@ -531,6 +552,8 @@ function UI.Window:layout() | ||||
| 	if not self.height then | ||||
| 		self.height = self.parent.height - self.y + 1 | ||||
| 	end | ||||
|  | ||||
| 	self:reposition(self.x, self.y, self.width, self.height) | ||||
| end | ||||
|  | ||||
| -- Called when the window's parent has be assigned | ||||
| @@ -539,23 +562,6 @@ function UI.Window:setParent() | ||||
| 	self.ox, self.oy = self.x, self.y | ||||
|  | ||||
| 	self:layout() | ||||
|  | ||||
| 	-- Experimental | ||||
| 	-- Inherit properties from the parent container | ||||
| 	-- does this need to be in reverse order ? | ||||
| 	local m = getmetatable(self)  -- get the class for this instance | ||||
| 	repeat | ||||
| 		if m.inherits then | ||||
| 			for k, v in pairs(m.inherits) do | ||||
| 				local value = self.parent:getProperty(v) | ||||
| 				if value then | ||||
| 					self[k] = value | ||||
| 				end | ||||
| 			end | ||||
| 		end | ||||
| 		m = m._base | ||||
| 	until not m | ||||
|  | ||||
| 	self:initChildren() | ||||
| end | ||||
|  | ||||
| @@ -566,12 +572,33 @@ function UI.Window:resize() | ||||
| 	self:layout() | ||||
|  | ||||
| 	if self.children then | ||||
| 		for _,child in ipairs(self.children) do | ||||
| 		for child in self:eachChild() do | ||||
| 			child:resize() | ||||
| 		end | ||||
| 	end | ||||
| end | ||||
|  | ||||
| function UI.Window:reposition(x, y, w, h) | ||||
| 	if not self.lines then | ||||
| 		Canvas.init(self, { | ||||
| 			x = x, | ||||
| 			y = y, | ||||
| 			width = w, | ||||
| 			height = h, | ||||
| 			isColor = self.parent.isColor, | ||||
| 		}) | ||||
| 	else | ||||
| 		self:move(x, y) | ||||
| 		Canvas.resize(self, w, h) | ||||
| 	end | ||||
| end | ||||
|  | ||||
| function UI.Window:raise() | ||||
| 	Array.removeByValue(self.parent.children, self) | ||||
| 	table.insert(self.parent.children, self) | ||||
| 	self:dirty(true) | ||||
| end | ||||
|  | ||||
| UI.Window.docs.add = [[add(TABLE) | ||||
| Add element(s) to a window. Example: | ||||
| page:add({ | ||||
| @@ -584,6 +611,20 @@ function UI.Window:add(children) | ||||
| 	self:initChildren() | ||||
| end | ||||
|  | ||||
| function UI.Window:eachChild() | ||||
| 	local c = self.children and Util.shallowCopy(self.children) | ||||
| 	local i = 0 | ||||
| 	return function() | ||||
| 		i = i + 1 | ||||
| 		return c and c[i] | ||||
| 	end | ||||
| end | ||||
|  | ||||
| function UI.Window:remove() | ||||
| 	Array.removeByValue(self.parent.children, self) | ||||
| 	self.parent:dirty(true) | ||||
| end | ||||
|  | ||||
| function UI.Window:getCursorPos() | ||||
| 	return self.cursorX, self.cursorY | ||||
| end | ||||
| @@ -601,15 +642,17 @@ end | ||||
| UI.Window.docs.draw = [[draw(VOID) | ||||
| Redraws the window in the internal buffer.]] | ||||
| function UI.Window:draw() | ||||
| 	self:clear(self.backgroundColor) | ||||
| 	if self.children then | ||||
| 		for _,child in pairs(self.children) do | ||||
| 	self:clear() | ||||
| 	self:drawChildren() | ||||
| end | ||||
|  | ||||
| function UI.Window:drawChildren() | ||||
| 	for child in self:eachChild() do | ||||
| 		if child.enabled then | ||||
| 			child:draw() | ||||
| 		end | ||||
| 	end | ||||
| end | ||||
| end | ||||
|  | ||||
| UI.Window.docs.getDoc = [[getDoc(STRING method) | ||||
| Gets the documentation for a method.]] | ||||
| @@ -634,19 +677,30 @@ end | ||||
|  | ||||
| function UI.Window:enable(...) | ||||
| 	self.enabled = true | ||||
| 	if self.children then | ||||
| 		for _,child in pairs(self.children) do | ||||
| 			child:enable(...) | ||||
| 	if self.transitionHint then | ||||
| 		self:addTransition(self.transitionHint) | ||||
| 	end | ||||
|  | ||||
| 	if self.modal then | ||||
| 		self:raise() | ||||
| 		self:capture(self) | ||||
| 	end | ||||
|  | ||||
| 	for child in self:eachChild() do | ||||
| 		child:enable(...) | ||||
| 	end | ||||
| end | ||||
|  | ||||
| function UI.Window:disable() | ||||
| 	self.enabled = false | ||||
| 	if self.children then | ||||
| 		for _,child in pairs(self.children) do | ||||
| 			child:disable() | ||||
| 	self.parent:dirty(true) | ||||
|  | ||||
| 	if self.modal then | ||||
| 		self:release(self) | ||||
| 	end | ||||
|  | ||||
| 	for child in self:eachChild() do | ||||
| 		child:disable() | ||||
| 	end | ||||
| end | ||||
|  | ||||
| @@ -656,13 +710,9 @@ function UI.Window:setTextScale(textScale) | ||||
| end | ||||
|  | ||||
| UI.Window.docs.clear = [[clear(opt COLOR bg, opt COLOR fg) | ||||
| Clears the window using the either the passed values or the defaults for that window.]] | ||||
| Clears the window using either the passed values or the defaults for that window.]] | ||||
| function UI.Window:clear(bg, fg) | ||||
| 	if self.canvas then | ||||
| 		self.canvas:clear(bg or self:getProperty('backgroundColor'), fg or self:getProperty('textColor')) | ||||
| 	else | ||||
| 		self:clearArea(1 + self.offx, 1 + self.offy, self.width, self.height, bg) | ||||
| 	end | ||||
| 	Canvas.clear(self, bg or self:getProperty('backgroundColor'), fg or self:getProperty('textColor')) | ||||
| end | ||||
|  | ||||
| function UI.Window:clearLine(y, bg) | ||||
| @@ -670,28 +720,20 @@ function UI.Window:clearLine(y, bg) | ||||
| end | ||||
|  | ||||
| function UI.Window:clearArea(x, y, width, height, bg) | ||||
| 	self:fillArea(x, y, width, height, ' ', bg) | ||||
| end | ||||
|  | ||||
| function UI.Window:fillArea(x, y, width, height, fillChar, bg, fg) | ||||
| 	if width > 0 then | ||||
| 		local filler = _rep(' ', width) | ||||
| 		local filler = _rep(fillChar, width) | ||||
| 		for i = 0, height - 1 do | ||||
| 			self:write(x, y + i, filler, bg) | ||||
| 			self:write(x, y + i, filler, bg, fg) | ||||
| 		end | ||||
| 	end | ||||
| end | ||||
|  | ||||
| function UI.Window:write(x, y, text, bg, fg) | ||||
| 	bg = bg or self.backgroundColor | ||||
| 	fg = fg or self.textColor | ||||
|  | ||||
| 	if self.canvas then | ||||
| 		self.canvas:write(x, y, text, bg or self:getProperty('backgroundColor'), fg or self:getProperty('textColor')) | ||||
| 	else | ||||
| 		x = x - self.offx | ||||
| 		y = y - self.offy | ||||
| 		if y <= self.height and y > 0 then | ||||
| 			self.parent:write( | ||||
| 				self.x + x - 1, self.y + y - 1, tostring(text), bg, fg) | ||||
| 		end | ||||
| 	end | ||||
| 	Canvas.write(self, x, y, text, bg or self:getProperty('backgroundColor'), fg or self:getProperty('textColor')) | ||||
| end | ||||
|  | ||||
| function UI.Window:centeredWrite(y, text, bg, fg) | ||||
| @@ -826,7 +868,8 @@ function UI.Window:pointToChild(x, y) | ||||
| 	x = x + self.offx - self.x + 1 | ||||
| 	y = y + self.offy - self.y + 1 | ||||
| 	if self.children then | ||||
| 		for _,child in pairs(self.children) do | ||||
| 		for i = #self.children, 1, -1 do | ||||
| 			local child = self.children[i] | ||||
| 			if child.enabled and not child.inactive and | ||||
| 				 x >= child.x and x < child.x + child.width and | ||||
| 				 y >= child.y and y < child.y + child.height then | ||||
| @@ -882,37 +925,29 @@ function UI.Window:focusFirst() | ||||
| 	end | ||||
| end | ||||
|  | ||||
| function UI.Window:refocus() | ||||
| 	local el = self | ||||
| 	while el do | ||||
| 		local focusables = el:getFocusables() | ||||
| 		if focusables[1] then | ||||
| 			self:setFocus(focusables[1]) | ||||
| 			break | ||||
| 		end | ||||
| 		el = el.parent | ||||
| 	end | ||||
| end | ||||
|  | ||||
| function UI.Window:scrollIntoView() | ||||
| 	local parent = self.parent | ||||
| 	local offx, offy = parent.offx, parent.offy | ||||
|  | ||||
| 	if self.x <= parent.offx then | ||||
| 		parent.offx = math.max(0, self.x - 1) | ||||
| 		if offx ~= parent.offx then | ||||
| 			parent:draw() | ||||
| 		end | ||||
| 	elseif self.x + self.width > parent.width + parent.offx then | ||||
| 		parent.offx = self.x + self.width - parent.width - 1 | ||||
| 		if offx ~= parent.offx then | ||||
| 			parent:draw() | ||||
| 		end | ||||
| 	end | ||||
|  | ||||
| 	-- TODO: fix | ||||
| 	local function setOffset(y) | ||||
| 		parent.offy = y | ||||
| 		if parent.canvas then | ||||
| 			parent.canvas.offy = parent.offy | ||||
| 		end | ||||
| 		if offy ~= parent.offy then | ||||
| 			parent:draw() | ||||
| 		end | ||||
| 	end | ||||
|  | ||||
| 	if self.y <= parent.offy then | ||||
| 		setOffset(math.max(0, self.y - 1)) | ||||
| @@ -921,43 +956,16 @@ function UI.Window:scrollIntoView() | ||||
| 	end | ||||
| end | ||||
|  | ||||
| function UI.Window:getCanvas() | ||||
| 	local el = self | ||||
| 	repeat | ||||
| 		if el.canvas then | ||||
| 			return el.canvas | ||||
| 		end | ||||
| 		el = el.parent | ||||
| 	until not el | ||||
| end | ||||
|  | ||||
| function UI.Window:addLayer(bg, fg) | ||||
| 	local canvas = self:getCanvas() | ||||
| 	local x, y = self.x, self.y | ||||
| 	local parent = self.parent | ||||
| 	while parent and not parent.canvas do | ||||
| 		x = x + parent.x - 1 | ||||
| 		y = y + parent.y - 1 | ||||
| 		parent = parent.parent | ||||
| 	end | ||||
| 	canvas = canvas:addLayer({ | ||||
| 		x = x, y = y, height = self.height, width = self.width | ||||
| 	}, bg, fg) | ||||
|  | ||||
| 	canvas:clear(bg or self.backgroundColor, fg or self.textColor) | ||||
| 	return canvas | ||||
| end | ||||
|  | ||||
| function UI.Window:addTransition(effect, args) | ||||
| 	if self.parent then | ||||
| 		args = args or { } | ||||
| 		if not args.x then -- not good | ||||
| 			args.x, args.y = self.x, self.y -- getPosition(self) | ||||
| 			args.x, args.y = self.x, self.y | ||||
| 			args.width = self.width | ||||
| 			args.height = self.height | ||||
| 			args.canvas = self | ||||
| 		end | ||||
|  | ||||
| 		args.canvas = args.canvas or self.canvas | ||||
| 		self.parent:addTransition(effect, args) | ||||
| 	end | ||||
| end | ||||
| @@ -991,7 +999,16 @@ function UI.Window:getProperty(property) | ||||
| end | ||||
|  | ||||
| function UI.Window:find(uid) | ||||
| 	return self.children and Util.find(self.children, 'uid', uid) | ||||
| 	local el = self.children and Util.find(self.children, 'uid', uid) | ||||
| 	if not el then | ||||
| 		for child in self:eachChild() do | ||||
| 			el = child:find(uid) | ||||
| 			if el then | ||||
| 				break | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
| 	return el | ||||
| end | ||||
|  | ||||
| function UI.Window:eventHandler() | ||||
| @@ -1010,18 +1027,14 @@ UI.Device.defaults = { | ||||
| function UI.Device:postInit() | ||||
| 	self.device = self.device or term.current() | ||||
|  | ||||
| 	--if self.deviceType then | ||||
| 	--	self.device = device[self.deviceType] | ||||
| 	--end | ||||
|  | ||||
| 	if not self.device.setTextScale then | ||||
| 		self.device.setTextScale = function() end | ||||
| 	end | ||||
|  | ||||
| 	self.device.setTextScale(self.textScale) | ||||
| 	self.width, self.height = self.device.getSize() | ||||
|  | ||||
| 	self.isColor = self.device.isColor() | ||||
| 	Canvas.init(self, { isColor = self.isColor }) | ||||
|  | ||||
| 	UI.devices[self.device.side or 'terminal'] = self | ||||
| end | ||||
| @@ -1031,8 +1044,8 @@ function UI.Device:resize() | ||||
| 	self.width, self.height = self.device.getSize() | ||||
| 	self.lines = { } | ||||
| 	-- TODO: resize all pages added to this device | ||||
| 	self.canvas:resize(self.width, self.height) | ||||
| 	self.canvas:clear(self.backgroundColor, self.textColor) | ||||
| 	Canvas.resize(self, self.width, self.height) | ||||
| 	Canvas.clear(self, self.backgroundColor, self.textColor) | ||||
| end | ||||
|  | ||||
| function UI.Device:setCursorPos(x, y) | ||||
| @@ -1069,7 +1082,7 @@ function UI.Device:addTransition(effect, args) | ||||
| 	args = args or { } | ||||
| 	args.ex = args.x + args.width - 1 | ||||
| 	args.ey = args.y + args.height - 1 | ||||
| 	args.canvas = args.canvas or self.canvas | ||||
| 	args.canvas = args.canvas or self | ||||
|  | ||||
| 	if type(effect) == 'string' then | ||||
| 		effect = Transition[effect] | ||||
| @@ -1078,10 +1091,13 @@ function UI.Device:addTransition(effect, args) | ||||
| 		end | ||||
| 	end | ||||
|  | ||||
| 	table.insert(self.transitions, { update = effect(args), args = args }) | ||||
| 	table.insert(self.transitions, { effect = effect, args = args }) | ||||
| end | ||||
|  | ||||
| function UI.Device:runTransitions(transitions, canvas) | ||||
| function UI.Device:runTransitions(transitions) | ||||
| 	for _,k in pairs(transitions) do | ||||
| 		k.update = k.effect(k.args) | ||||
| 	end | ||||
| 	while true do | ||||
| 		for _,k in ipairs(Util.keys(transitions)) do | ||||
| 			local transition = transitions[k] | ||||
| @@ -1089,7 +1105,7 @@ function UI.Device:runTransitions(transitions, canvas) | ||||
| 				transitions[k] = nil | ||||
| 			end | ||||
| 		end | ||||
| 		canvas:render(self.device) | ||||
| 		self.currentPage:render(self.device) | ||||
| 		if Util.empty(transitions) then | ||||
| 			break | ||||
| 		end | ||||
| @@ -1105,9 +1121,9 @@ function UI.Device:sync() | ||||
| 		self.device.setCursorBlink(false) | ||||
| 	end | ||||
|  | ||||
| 	self.canvas:render(self.device) | ||||
| 	self.currentPage:render(self.device) | ||||
| 	if transitions then | ||||
| 		self:runTransitions(transitions, self.canvas) | ||||
| 		self:runTransitions(transitions) | ||||
| 	end | ||||
|  | ||||
| 	if self:getCursorBlink() then | ||||
|   | ||||
| @@ -9,7 +9,6 @@ local colors = _G.colors | ||||
|  | ||||
| local Canvas = class() | ||||
|  | ||||
| Canvas.__visualize = false | ||||
| Canvas.colorPalette = { } | ||||
| Canvas.darkPalette = { } | ||||
| Canvas.grayscalePalette = { } | ||||
| @@ -22,15 +21,17 @@ end | ||||
|  | ||||
| --[[ | ||||
| 	A canvas can have more lines than canvas.height in order to scroll | ||||
| ]] | ||||
|  | ||||
| 	TODO: finish vertical scrolling | ||||
| ]] | ||||
| function Canvas:init(args) | ||||
| 	self.x = 1 | ||||
| 	self.y = 1 | ||||
| 	self.layers = { } | ||||
| 	self.bg = colors.black | ||||
| 	self.fg = colors.white | ||||
|  | ||||
| 	Util.merge(self, args) | ||||
|  | ||||
| 	self.x = self.x or 1 | ||||
| 	self.y = self.y or 1 | ||||
| 	self.ex = self.x + self.width - 1 | ||||
| 	self.ey = self.y + self.height - 1 | ||||
|  | ||||
| @@ -46,15 +47,30 @@ function Canvas:init(args) | ||||
| 	for i = 1, self.height do | ||||
| 		self.lines[i] = { } | ||||
| 	end | ||||
|  | ||||
| 	self:clear() | ||||
| end | ||||
|  | ||||
| function Canvas:move(x, y) | ||||
| 	self.x, self.y = x, y | ||||
| 	self.ex = self.x + self.width - 1 | ||||
| 	self.ey = self.y + self.height - 1 | ||||
| 	if self.parent then | ||||
| 		self.parent:dirty(true) | ||||
| 	end | ||||
| end | ||||
|  | ||||
| function Canvas:resize(w, h) | ||||
| 	self:resizeBuffer(w, h) | ||||
|  | ||||
| 	self.ex = self.x + w - 1 | ||||
| 	self.ey = self.y + h - 1 | ||||
| 	self.width = w | ||||
| 	self.height = h | ||||
| end | ||||
|  | ||||
| -- resize the canvas buffer - not the canvas itself | ||||
| function Canvas:resizeBuffer(w, h) | ||||
| 	for i = #self.lines, h do | ||||
| 		self.lines[i] = { } | ||||
| 		self:clearLine(i) | ||||
| @@ -66,26 +82,24 @@ function Canvas:resize(w, h) | ||||
|  | ||||
| 	if w < self.width then | ||||
| 		for i = 1, h do | ||||
| 			self.lines[i].text = _sub(self.lines[i].text, 1, w) | ||||
| 			self.lines[i].fg = _sub(self.lines[i].fg, 1, w) | ||||
| 			self.lines[i].bg = _sub(self.lines[i].bg, 1, w) | ||||
| 			local ln = self.lines[i] | ||||
| 			ln.text = _sub(ln.text, 1, w) | ||||
| 			ln.fg = _sub(ln.fg, 1, w) | ||||
| 			ln.bg = _sub(ln.bg, 1, w) | ||||
| 		end | ||||
| 	elseif w > self.width then | ||||
| 		local d = w - self.width | ||||
| 		local text = _rep(' ', d) | ||||
| 		local fg = _rep(self.palette[self.fg or colors.white], d) | ||||
| 		local bg = _rep(self.palette[self.bg or colors.black], d) | ||||
| 		local fg = _rep(self.palette[self.fg], d) | ||||
| 		local bg = _rep(self.palette[self.bg], d) | ||||
| 		for i = 1, h do | ||||
| 			self.lines[i].text = self.lines[i].text .. text | ||||
| 			self.lines[i].fg = self.lines[i].fg .. fg | ||||
| 			self.lines[i].bg = self.lines[i].bg .. bg | ||||
| 			local ln = self.lines[i] | ||||
| 			ln.text = ln.text .. text | ||||
| 			ln.fg = ln.fg .. fg | ||||
| 			ln.bg = ln.bg .. bg | ||||
| 			ln.dirty = true | ||||
| 		end | ||||
| 	end | ||||
|  | ||||
| 	self.ex = self.x + w - 1 | ||||
| 	self.ey = self.y + h - 1 | ||||
| 	self.width = w | ||||
| 	self.height = h | ||||
| end | ||||
|  | ||||
| function Canvas:copy() | ||||
| @@ -113,22 +127,25 @@ function Canvas:addLayer(layer) | ||||
| 		isColor = self.isColor, | ||||
| 	}) | ||||
| 	canvas.parent = self | ||||
| 	table.insert(self.layers, canvas) | ||||
| 	if not self.children then | ||||
| 		self.children = { } | ||||
| 	end | ||||
| 	table.insert(self.children, canvas) | ||||
| 	return canvas | ||||
| end | ||||
|  | ||||
| function Canvas:removeLayer() | ||||
| 	for k, layer in pairs(self.parent.layers) do | ||||
| 	for k, layer in pairs(self.parent.children) do | ||||
| 		if layer == self then | ||||
| 			self:setVisible(false) | ||||
| 			table.remove(self.parent.layers, k) | ||||
| 			table.remove(self.parent.children, k) | ||||
| 			break | ||||
| 		end | ||||
| 	end | ||||
| end | ||||
|  | ||||
| function Canvas:setVisible(visible) | ||||
| 	self.visible = visible | ||||
| 	self.visible = visible  -- TODO: use self.active = visible | ||||
| 	if not visible and self.parent then | ||||
| 		self.parent:dirty() | ||||
| 		-- TODO: set parent's lines to dirty for each line in self | ||||
| @@ -137,11 +154,10 @@ end | ||||
|  | ||||
| -- Push a layer to the top | ||||
| function Canvas:raise() | ||||
| 	if self.parent then | ||||
| 		local layers = self.parent.layers or { } | ||||
| 		for k, v in pairs(layers) do | ||||
| 	if self.parent and self.parent.children then | ||||
| 		for k, v in pairs(self.parent.children) do | ||||
| 			if v == self then | ||||
| 				table.insert(layers, table.remove(layers, k)) | ||||
| 				table.insert(self.parent.children, table.remove(self.parent.children, k)) | ||||
| 				break | ||||
| 			end | ||||
| 		end | ||||
| @@ -224,15 +240,15 @@ function Canvas:writeLine(y, text, fg, bg) | ||||
| end | ||||
|  | ||||
| function Canvas:clearLine(y, bg, fg) | ||||
| 	fg = _rep(self.palette[fg or colors.white], self.width) | ||||
| 	bg = _rep(self.palette[bg or colors.black], self.width) | ||||
| 	fg = _rep(self.palette[fg or self.fg], self.width) | ||||
| 	bg = _rep(self.palette[bg or self.bg], self.width) | ||||
| 	self:writeLine(y, _rep(' ', self.width), fg, bg) | ||||
| end | ||||
|  | ||||
| function Canvas:clear(bg, fg) | ||||
| 	local text = _rep(' ', self.width) | ||||
| 	fg = _rep(self.palette[fg or colors.white], self.width) | ||||
| 	bg = _rep(self.palette[bg or colors.black], self.width) | ||||
| 	fg = _rep(self.palette[fg or self.fg], self.width) | ||||
| 	bg = _rep(self.palette[bg or self.bg], self.width) | ||||
| 	for i = 1, #self.lines do | ||||
| 		self:writeLine(i, text, fg, bg) | ||||
| 	end | ||||
| @@ -246,13 +262,16 @@ function Canvas:isDirty() | ||||
| 	end | ||||
| end | ||||
|  | ||||
| function Canvas:dirty() | ||||
| function Canvas:dirty(includingChildren) | ||||
| 	if self.lines then | ||||
| 		for i = 1, #self.lines do | ||||
| 			self.lines[i].dirty = true | ||||
| 		end | ||||
| 	if self.layers then | ||||
| 		for _, canvas in pairs(self.layers) do | ||||
| 			canvas:dirty() | ||||
|  | ||||
| 		if includingChildren and self.children then | ||||
| 			for _, child in pairs(self.children) do | ||||
| 				child:dirty(true) | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
| end | ||||
| @@ -280,86 +299,76 @@ end | ||||
|  | ||||
| function Canvas:render(device) | ||||
| 	local offset = { x = 0, y = 0 } | ||||
|  | ||||
| 	-- WIP | ||||
| 	local function getRegion(canvas) | ||||
| 		local region | ||||
| 		if canvas.parent then | ||||
| 			region = getRegion(canvas.parent) | ||||
| 		else | ||||
| 			region = Region.new(self.x, self.y, self.ex, self.ey) | ||||
| 		end | ||||
| 		offset.x = offset.x + canvas.x - 1 | ||||
| 		offset.y = offset.y + canvas.y - 1 | ||||
| 		-- clip against parent | ||||
| 		return region | ||||
| 	end | ||||
|  | ||||
| 	-- this code works - but is all kinds of wrong | ||||
| 	-- adding a margin to UI.Page will cause issues | ||||
| 	-- and could be clipping issues | ||||
| 	offset = { x = self.x - 1, y = self.y - 1 } | ||||
| 	local parent = self.parent | ||||
| 	while parent do | ||||
| 		offset.x = offset.x + parent.x - 1 | ||||
| 		offset.y = offset.y + parent.y - 1 | ||||
| 		parent = parent.parent | ||||
| 	end | ||||
| 	if #self.layers > 0 then | ||||
| 		self:__renderLayers(device, offset) | ||||
| 	else | ||||
| 		self:__blitRect(device, nil, { | ||||
| 			x = self.x + offset.x, | ||||
| 			y = self.y + offset.y | ||||
| 		}) | ||||
| 		self:clean() | ||||
| 	end | ||||
|  | ||||
| 	-- TODO: need to clip if there is a parent | ||||
| 	--self.regions = Region.new(self.x + offset.x, self.y + offset.y, self.ex + offset.x, self.ey + offset.y) | ||||
| 	--self:__renderLayers(device, offset) | ||||
|  | ||||
| 	self.regions = Region.new(self.x, self.y, self.ex, self.ey) | ||||
| 	self:__renderLayers(device, { x = self.x - 1, y = self.y - 1 }) | ||||
| end | ||||
|  | ||||
| -- regions are comprised of absolute values that coorespond to the output device. | ||||
| -- regions are comprised of absolute values that correspond to the output device. | ||||
| -- canvases have coordinates relative to their parent. | ||||
| -- canvas layer's stacking order is determined by the position within the array. | ||||
| -- layers in the beginning of the array are overlayed by layers further down in | ||||
| -- the array. | ||||
| function Canvas:__renderLayers(device, offset) | ||||
| 	if #self.layers > 0 then | ||||
| 		self.regions = self.regions or Region.new(self.x + offset.x, self.y + offset.y, self.ex + offset.x, self.ey + offset.y) | ||||
|  | ||||
| 		for i = 1, #self.layers do | ||||
| 			local canvas = self.layers[i] | ||||
| 			if canvas.visible then | ||||
|  | ||||
| 				-- punch out this area from the parent's canvas | ||||
| 				self:__punch(canvas, offset) | ||||
|  | ||||
| 	if self.children then | ||||
| 		for i = #self.children, 1, -1 do | ||||
| 			local canvas = self.children[i] | ||||
| 			if canvas.visible or canvas.enabled then | ||||
| 				-- get the area to render for this layer | ||||
| 				canvas.regions = Region.new( | ||||
| 					canvas.x + offset.x, | ||||
| 					canvas.y + offset.y, | ||||
| 					canvas.ex + offset.x, | ||||
| 					canvas.ey + offset.y) | ||||
| 					canvas.x + offset.x - (self.offx or 0), | ||||
| 					canvas.y + offset.y - (self.offy or 0), | ||||
| 					canvas.ex + offset.x - (self.offx or 0), | ||||
| 					canvas.ey + offset.y - (self.offy or 0)) | ||||
|  | ||||
| 				-- contain within parent | ||||
| 				canvas.regions:andRegion(self.regions) | ||||
|  | ||||
| 				-- punch out this area from the parent's canvas | ||||
| 				self.regions:subRect( | ||||
| 					canvas.x + offset.x - (self.offx or 0), | ||||
| 					canvas.y + offset.y - (self.offy or 0), | ||||
| 					canvas.ex + offset.x - (self.offx or 0), | ||||
| 					canvas.ey + offset.y - (self.offy or 0)) | ||||
|  | ||||
| 				-- punch out any layers that overlap this one | ||||
| 				for j  = i + 1, #self.layers do | ||||
| 					if self.layers[j].visible then | ||||
| 						canvas:__punch(self.layers[j], offset) | ||||
| 					end | ||||
| 				end | ||||
| 				if #canvas.regions.region > 0 then | ||||
| 					canvas:__renderLayers(device, { | ||||
| 						x = canvas.x + offset.x - 1, | ||||
| 						y = canvas.y + offset.y - 1, | ||||
| 						x = canvas.x + offset.x - 1 - (self.offx or 0), | ||||
| 						y = canvas.y + offset.y - 1 - (self.offy or 0), | ||||
| 					}) | ||||
| 				end | ||||
| 				canvas.regions = nil | ||||
| 			end | ||||
| 		end | ||||
|  | ||||
| 		self:__blitClipped(device, offset) | ||||
| 		self.regions = nil | ||||
|  | ||||
| 	elseif self.regions and #self.regions.region > 0 then | ||||
| 		self:__blitClipped(device, offset) | ||||
| 		self.regions = nil | ||||
|  | ||||
| 	else | ||||
| 		self:__blitRect(device, nil, { | ||||
| 			x = self.x + offset.x, | ||||
| 			y = self.y + offset.y | ||||
| 		}) | ||||
| 		self.regions = nil | ||||
| 	end | ||||
| 	self:clean() | ||||
| end | ||||
|  | ||||
| function Canvas:__blitClipped(device, offset) | ||||
| 	if self.parent then | ||||
| 		-- contain the rendered region in the parent's region | ||||
| 		local p = Region.new(1, 1, | ||||
| 			self.parent.width + offset.x - self.x + 1, | ||||
| 			self.parent.height + offset.y - self.y + 1) | ||||
| 		self.regions:andRegion(p) | ||||
| 	end | ||||
|  | ||||
| 	for _,region in ipairs(self.regions.region) do | ||||
| @@ -370,14 +379,9 @@ function Canvas:__blitClipped(device, offset) | ||||
| 			  ey = region[4] - offset.y }, | ||||
| 			{ x = region[1], y = region[2] }) | ||||
| 	end | ||||
| end | ||||
| 	self.regions = nil | ||||
|  | ||||
| function Canvas:__punch(rect, offset) | ||||
| 	self.regions:subRect( | ||||
| 		rect.x + offset.x, | ||||
| 		rect.y + offset.y, | ||||
| 		rect.ex + offset.x, | ||||
| 		rect.ey + offset.y) | ||||
| 	self:clean() | ||||
| end | ||||
|  | ||||
| -- performance can probably be improved by using one more buffer tied to the device | ||||
| @@ -386,7 +390,7 @@ function Canvas:__blitRect(device, src, tgt) | ||||
| 	tgt = tgt or self | ||||
|  | ||||
| 	-- for visualizing updates on the screen | ||||
| 	if Canvas.__visualize then | ||||
| 	if Canvas.__visualize or self.visualize then | ||||
| 		local drew | ||||
| 		local t  = _rep(' ', src.ex-src.x + 1) | ||||
| 		local bg = _rep(2,   src.ex-src.x + 1) | ||||
| @@ -399,8 +403,8 @@ function Canvas:__blitRect(device, src, tgt) | ||||
| 			end | ||||
| 		end | ||||
| 		if drew then | ||||
| 			local t = os.clock() | ||||
| 			repeat until os.clock()-t > .2 | ||||
| 			local c = os.clock() | ||||
| 			repeat until os.clock()-c > .03 | ||||
| 		end | ||||
| 	end | ||||
| 	for i = 0, src.ey - src.y do | ||||
| @@ -418,4 +422,16 @@ function Canvas:__blitRect(device, src, tgt) | ||||
| 	end | ||||
| end | ||||
|  | ||||
| if not ({ ... })[1] then | ||||
| 	local UI = require('opus.ui') | ||||
|  | ||||
| 	UI:setPage(UI.Page { | ||||
| 		button = UI.Button { | ||||
| 			x = 5, y = 5, | ||||
| 			text = 'abc' | ||||
| 		} | ||||
| 	}) | ||||
| 	UI:start() | ||||
| end | ||||
|  | ||||
| return Canvas | ||||
|   | ||||
| @@ -1,32 +0,0 @@ | ||||
| local class = require('opus.class') | ||||
| local UI    = require('opus.ui') | ||||
|  | ||||
| UI.ActiveLayer = class(UI.Window) | ||||
| UI.ActiveLayer.defaults = { | ||||
| 	UIElement = 'ActiveLayer', | ||||
| } | ||||
| function UI.ActiveLayer:layout() | ||||
| 	UI.Window.layout(self) | ||||
| 	if not self.canvas then | ||||
| 		self.canvas = self:addLayer() | ||||
| 	else | ||||
| 		self.canvas:resize(self.width, self.height) | ||||
| 	end | ||||
| end | ||||
|  | ||||
| function UI.ActiveLayer:enable(...) | ||||
| 	self.canvas:raise() | ||||
| 	self.canvas:setVisible(true) | ||||
| 	UI.Window.enable(self, ...) | ||||
| 	if self.parent.transitionHint then | ||||
| 		self:addTransition(self.parent.transitionHint) | ||||
| 	end | ||||
| 	self:focusFirst() | ||||
| end | ||||
|  | ||||
| function UI.ActiveLayer:disable() | ||||
| 	if self.canvas then | ||||
| 		self.canvas:setVisible(false) | ||||
| 	end | ||||
| 	UI.Window.disable(self) | ||||
| end | ||||
| @@ -35,11 +35,11 @@ function UI.Button:draw() | ||||
| 	local bg = self.backgroundColor | ||||
| 	local ind = ' ' | ||||
| 	if self.focused then | ||||
| 		bg = self.backgroundFocusColor | ||||
| 		fg = self.textFocusColor | ||||
| 		bg = self:getProperty('backgroundFocusColor') | ||||
| 		fg = self:getProperty('textFocusColor') | ||||
| 		ind = self.focusIndicator | ||||
| 	elseif self.inactive then | ||||
| 		fg = self.textInactiveColor | ||||
| 		fg = self:getProperty('textInactiveColor') | ||||
| 	end | ||||
| 	local text = ind .. self.text .. ' ' | ||||
| 	if self.centered then | ||||
|   | ||||
| @@ -21,9 +21,6 @@ UI.Checkbox.defaults = { | ||||
| 		mouse_click = 'checkbox_toggle', | ||||
| 	} | ||||
| } | ||||
| UI.Checkbox.inherits = { | ||||
| 	labelBackgroundColor = 'backgroundColor', | ||||
| } | ||||
| function UI.Checkbox:postInit() | ||||
| 	self.width = self.label and #self.label + 4 or 3 | ||||
| end | ||||
|   | ||||
| @@ -11,8 +11,8 @@ UI.Chooser.defaults = { | ||||
| 	nochoice = 'Select', | ||||
| 	backgroundFocusColor = colors.lightGray, | ||||
| 	textInactiveColor = colors.gray, | ||||
| 	leftIndicator = UI.extChars and '\17' or '<', | ||||
| 	rightIndicator = UI.extChars and '\16' or '>', | ||||
| 	leftIndicator = UI.extChars and '\171' or '<', | ||||
| 	rightIndicator = UI.extChars and '\187' or '>', | ||||
| 	height = 1, | ||||
| 	accelerators = { | ||||
| 		space = 'choice_next', | ||||
|   | ||||
| @@ -1,4 +1,3 @@ | ||||
| local Canvas = require('opus.ui.canvas') | ||||
| local class  = require('opus.class') | ||||
| local UI     = require('opus.ui') | ||||
|  | ||||
| @@ -19,14 +18,14 @@ function UI.Dialog:postInit() | ||||
| end | ||||
|  | ||||
| function UI.Dialog:show(...) | ||||
| 	local canvas = self.parent:getCanvas() | ||||
| 	local canvas = self.parent | ||||
| 	self.oldPalette = canvas.palette | ||||
| 	canvas:applyPalette(Canvas.darkPalette) | ||||
| 	canvas:applyPalette(self.darkPalette) | ||||
| 	UI.SlideOut.show(self, ...) | ||||
| end | ||||
|  | ||||
| function UI.Dialog:hide(...) | ||||
| 	self.parent:getCanvas().palette = self.oldPalette | ||||
| 	self.parent.palette = self.oldPalette | ||||
| 	UI.SlideOut.hide(self, ...) | ||||
| 	self.parent:draw() | ||||
| end | ||||
| @@ -37,3 +36,30 @@ function UI.Dialog:eventHandler(event) | ||||
| 	end | ||||
| 	return UI.SlideOut.eventHandler(self, event) | ||||
| end | ||||
|  | ||||
| function UI.Dialog.example() | ||||
| 	return UI.Dialog { | ||||
| 		title = 'Enter Starting Level', | ||||
| 		height = 7, | ||||
| 		form = UI.Form { | ||||
| 			y = 3, x = 2, height = 4, | ||||
| 			event = 'setStartLevel', | ||||
| 			cancelEvent = 'slide_hide', | ||||
| 			text = UI.Text { | ||||
| 				x = 5, y = 1, width = 20, | ||||
| 				textColor = colors.gray, | ||||
| 			}, | ||||
| 			textEntry = UI.TextEntry { | ||||
| 				formKey = 'level', | ||||
| 				x = 15, y = 1, width = 7, | ||||
| 			}, | ||||
| 		}, | ||||
| 		statusBar = UI.StatusBar(), | ||||
| 		enable = function(self) | ||||
| 			require('opus.event').onTimeout(0, function() | ||||
| 				self:show() | ||||
| 				self:sync() | ||||
| 			end) | ||||
| 		end, | ||||
| 	} | ||||
| end | ||||
|   | ||||
| @@ -32,42 +32,38 @@ function UI.DropMenu:layout() | ||||
| 	self.height = #self.children + 1 | ||||
| 	self.width = maxWidth + 2 | ||||
|  | ||||
| 	if not self.canvas then | ||||
| 		self.canvas = self:addLayer() | ||||
| 	else | ||||
| 		self.canvas:resize(self.width, self.height) | ||||
| 	if self.x + self.width > self.parent.width then | ||||
| 		self.x = self.parent.width - self.width + 1 | ||||
| 	end | ||||
|  | ||||
| 	self:reposition(self.x, self.y, self.width, self.height) | ||||
| end | ||||
|  | ||||
| function UI.DropMenu:enable() | ||||
| 	for _,c in pairs(self.children) do | ||||
| 		if not c.spacer then | ||||
| 			c.inactive = not self:getActive(c) | ||||
| 		end | ||||
| 	end | ||||
|  | ||||
| function UI.DropMenu:show(x, y) | ||||
| 	self.x, self.y = x, y | ||||
| 	self.canvas:move(x, y) | ||||
| 	self.canvas:setVisible(true) | ||||
|  | ||||
| 	UI.Window.enable(self) | ||||
|  | ||||
| 	self:draw() | ||||
| 	self:capture(self) | ||||
| 	self:focusFirst() | ||||
| 	self:draw() | ||||
| end | ||||
|  | ||||
| function UI.DropMenu:hide() | ||||
| 	self:disable() | ||||
| 	self.canvas:setVisible(false) | ||||
| 	self:release(self) | ||||
| function UI.DropMenu:disable() | ||||
| 	UI.Window.disable(self) | ||||
| 	self:remove() | ||||
| end | ||||
|  | ||||
| function UI.DropMenu:eventHandler(event) | ||||
| 	if event.type == 'focus_lost' and self.enabled then | ||||
| 		if not Util.contains(self.children, event.focused) then | ||||
| 			self:hide() | ||||
| 			self:disable() | ||||
| 		end | ||||
| 	elseif event.type == 'mouse_out' and self.enabled then | ||||
| 		self:hide() | ||||
| 		self:refocus() | ||||
| 		self:disable() | ||||
| 		self:setFocus(self.parent:find(self.lastFocus)) | ||||
| 	else | ||||
| 		return UI.MenuBar.eventHandler(self, event) | ||||
| 	end | ||||
| @@ -83,6 +79,15 @@ function UI.DropMenu.example() | ||||
| 					{ spacer = true }, | ||||
| 					{ text = 'Quit        ^q', event = 'quit'   }, | ||||
| 			} }, | ||||
| 			{ text = 'Edit', dropdown = { | ||||
| 				{ text = 'Copy',           event = 'run' }, | ||||
| 				{ text = 'Paste        s', event = 'shell'  }, | ||||
| 			} }, | ||||
| 			{ text = '\187', | ||||
| 				x = -3, | ||||
| 				dropdown = { | ||||
| 					{ text = 'Associations', event = 'associate' }, | ||||
| 			} }, | ||||
| 		} | ||||
| 	} | ||||
| end | ||||
|   | ||||
| @@ -14,7 +14,7 @@ UI.DropMenuItem.defaults = { | ||||
| } | ||||
| function UI.DropMenuItem:eventHandler(event) | ||||
| 	if event.type == 'button_activate' then | ||||
| 		self.parent:hide() | ||||
| 		self.parent:disable() | ||||
| 	end | ||||
| 	return UI.Button.eventHandler(self, event) | ||||
| end | ||||
|   | ||||
| @@ -18,45 +18,33 @@ UI.Embedded.defaults = { | ||||
| function UI.Embedded:setParent() | ||||
| 	UI.Window.setParent(self) | ||||
|  | ||||
| 	function self.render() | ||||
| 		self:sync() | ||||
| 	end | ||||
| 	self.win = Terminal.window(UI.term.device, self.x, self.y, self.width, self.height, false) | ||||
| 	self.win.canvas = self | ||||
| 	self.win.setMaxScroll(self.maxScroll) | ||||
|  | ||||
| 	local canvas = self:getCanvas() | ||||
| 	self.win.getCanvas().parent = canvas | ||||
| 	table.insert(canvas.layers, self.win.getCanvas()) | ||||
| 	self.canvas = self.win.getCanvas() | ||||
|  | ||||
| 	self.win.setCursorPos(1, 1) | ||||
| 	self.win.setBackgroundColor(self.backgroundColor) | ||||
| 	self.win.setTextColor(self.textColor) | ||||
| 	self.win.clear() | ||||
| end | ||||
|  | ||||
| function UI.Embedded:layout() | ||||
| 	UI.Window.layout(self) | ||||
| 	if self.win then | ||||
| 		self.win.reposition(self.x, self.y, self.width, self.height) | ||||
| 	end | ||||
| function UI.Embedded:draw() | ||||
| 	self:dirty() | ||||
| end | ||||
|  | ||||
| function UI.Embedded:draw() | ||||
| 	self.canvas:dirty() | ||||
| function UI.Embedded:focus() | ||||
| 	-- allow scrolling | ||||
| end | ||||
|  | ||||
| function UI.Embedded:enable() | ||||
| 	self.canvas:setVisible(true) | ||||
| 	self.canvas:raise() | ||||
| 	if self.visible then | ||||
| 		-- the window will automatically update on changes | ||||
| 		-- the canvas does not need to be rendereed | ||||
| 		self.win.setVisible(true) | ||||
| 	end | ||||
| 	UI.Window.enable(self) | ||||
| 	self.canvas:dirty() | ||||
| 	self.win.setVisible(true) | ||||
| 	self:dirty() | ||||
| end | ||||
|  | ||||
| function UI.Embedded:disable() | ||||
| 	self.canvas:setVisible(false) | ||||
| 	self.win.setVisible(false) | ||||
| 	UI.Window.disable(self) | ||||
| end | ||||
| @@ -71,17 +59,12 @@ function UI.Embedded:eventHandler(event) | ||||
| 	end | ||||
| end | ||||
|  | ||||
| function UI.Embedded:focus() | ||||
| 	-- allow scrolling | ||||
| end | ||||
|  | ||||
| function UI.Embedded.example() | ||||
| 	local Event = require('opus.event') | ||||
| 	local Util  = require('opus.util') | ||||
| 	local term  = _G.term | ||||
|  | ||||
| 	return UI.Embedded { | ||||
| 		visible = true, | ||||
| 		enable = function (self) | ||||
| 			UI.Embedded.enable(self) | ||||
| 			Event.addRoutine(function() | ||||
|   | ||||
| @@ -60,7 +60,7 @@ UI.Grid.defaults = { | ||||
| 	textSelectedColor = colors.white, | ||||
| 	backgroundColor = colors.black, | ||||
| 	backgroundSelectedColor = colors.gray, | ||||
| 	headerBackgroundColor = colors.cyan, | ||||
| 	headerBackgroundColor = UI.colors.tertiary, | ||||
| 	headerTextColor = colors.white, | ||||
| 	headerSortColor = colors.yellow, | ||||
| 	unfocusedTextSelectedColor = colors.white, | ||||
|   | ||||
| @@ -1,19 +1,28 @@ | ||||
| local class = require('opus.class') | ||||
| local UI    = require('opus.ui') | ||||
| local Util  = require('opus.util') | ||||
|  | ||||
| local lookup = '0123456789abcdef' | ||||
|  | ||||
| -- handle files produced by Paint | ||||
| UI.Image = class(UI.Window) | ||||
| UI.Image.defaults = { | ||||
| 	UIElement = 'Image', | ||||
| 	event = 'button_press', | ||||
| } | ||||
| function UI.Image:setParent() | ||||
| 	if self.image then | ||||
| function UI.Image:postInit() | ||||
| 	if self.filename then | ||||
| 		self.image = Util.readLines(self.filename) | ||||
| 	end | ||||
|  | ||||
| 	if self.image and not (self.height or self.ey) then | ||||
| 		self.height = #self.image | ||||
| 	end | ||||
| 	if self.image and not self.width then | ||||
| 		self.width = #self.image[1] | ||||
| 	if self.image and not (self.width or self.ex) then | ||||
| 		for i = 1, self.height do | ||||
| 			self.width = math.max(self.width or 0, #self.image[i]) | ||||
| 		end | ||||
| 	end | ||||
| 	UI.Window.setParent(self) | ||||
| end | ||||
|  | ||||
| function UI.Image:draw() | ||||
| @@ -22,19 +31,22 @@ function UI.Image:draw() | ||||
| 		for y = 1, #self.image do | ||||
| 			local line = self.image[y] | ||||
| 			for x = 1, #line do | ||||
| 				local ch = line[x] | ||||
| 				if type(ch) == 'number' then | ||||
| 					if ch > 0 then | ||||
| 						self:write(x, y, ' ', ch) | ||||
| 					end | ||||
| 				else | ||||
| 					self:write(x, y, ch) | ||||
| 				local ch = lookup:find(line:sub(x, x)) | ||||
| 				if ch then | ||||
| 					self:write(x, y, ' ', 2 ^ (ch -1)) | ||||
| 				end | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
| 	self:drawChildren() | ||||
| end | ||||
|  | ||||
| function UI.Image:setImage(image) | ||||
| 	self.image = image | ||||
| end | ||||
|  | ||||
| function UI.Image.example() | ||||
| 	return UI.Image { | ||||
| 		filename = 'test.paint', | ||||
| 	} | ||||
| end | ||||
|   | ||||
| @@ -3,16 +3,6 @@ local UI    = require('opus.ui') | ||||
|  | ||||
| local colors = _G.colors | ||||
|  | ||||
| local function getPosition(element) | ||||
| 	local x, y = 1, 1 | ||||
| 	repeat | ||||
| 		x = element.x + x - 1 | ||||
| 		y = element.y + y - 1 | ||||
| 		element = element.parent | ||||
| 	until not element | ||||
| 	return x, y | ||||
| end | ||||
|  | ||||
| UI.MenuBar = class(UI.Window) | ||||
| UI.MenuBar.defaults = { | ||||
| 	UIElement = 'MenuBar', | ||||
| @@ -22,7 +12,6 @@ UI.MenuBar.defaults = { | ||||
| 	textColor = colors.black, | ||||
| 	spacing = 2, | ||||
| 	lastx = 1, | ||||
| 	showBackButton = false, | ||||
| 	buttonClass = 'MenuItem', | ||||
| } | ||||
| function UI.MenuBar:postInit() | ||||
| @@ -62,10 +51,6 @@ function UI.MenuBar:addButtons(buttons) | ||||
| 			else | ||||
| 				table.insert(self.children, button) | ||||
| 			end | ||||
|  | ||||
| 			if button.dropdown then | ||||
| 				button.dropmenu = UI.DropMenu { buttons = button.dropdown } | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
| 	if self.parent then | ||||
| @@ -78,23 +63,27 @@ function UI.MenuBar:getActive(menuItem) | ||||
| end | ||||
|  | ||||
| function UI.MenuBar:eventHandler(event) | ||||
| 	if event.type == 'button_press' and event.button.dropmenu then | ||||
| 		if event.button.dropmenu.enabled then | ||||
| 			event.button.dropmenu:hide() | ||||
| 			self:refocus() | ||||
| 			return true | ||||
| 		else | ||||
| 	if event.type == 'button_press' and event.button.dropdown then | ||||
| 		local function getPosition(element) | ||||
| 			local x, y = 1, 1 | ||||
| 			repeat | ||||
| 				x = element.x + x - 1 | ||||
| 				y = element.y + y - 1 | ||||
| 				element = element.parent | ||||
| 			until not element | ||||
| 			return x, y | ||||
| 		end | ||||
|  | ||||
| 		local x, y = getPosition(event.button) | ||||
| 			if x + event.button.dropmenu.width > self.width then | ||||
| 				x = self.width - event.button.dropmenu.width + 1 | ||||
| 			end | ||||
| 			for _,c in pairs(event.button.dropmenu.children) do | ||||
| 				if not c.spacer then | ||||
| 					c.inactive = not self:getActive(c) | ||||
| 				end | ||||
| 			end | ||||
| 			event.button.dropmenu:show(x, y + 1) | ||||
| 		end | ||||
|  | ||||
| 		local menu = UI.DropMenu { | ||||
| 			buttons = event.button.dropdown, | ||||
| 			x = x, | ||||
| 			y = y + 1, | ||||
| 			lastFocus = event.button.uid, | ||||
| 		} | ||||
| 		self.parent:add({ dropmenu = menu }) | ||||
|  | ||||
| 		return true | ||||
| 	end | ||||
| end | ||||
|   | ||||
| @@ -6,8 +6,6 @@ local colors = _G.colors | ||||
| UI.MenuItem = class(UI.Button) | ||||
| UI.MenuItem.defaults = { | ||||
| 	UIElement = 'MenuItem', | ||||
| 	textColor = colors.black, | ||||
| 	backgroundColor = colors.lightGray, | ||||
| 	textFocusColor = colors.white, | ||||
| 	backgroundFocusColor = colors.lightGray, | ||||
| } | ||||
|   | ||||
| @@ -5,17 +5,18 @@ UI.NftImage = class(UI.Window) | ||||
| UI.NftImage.defaults = { | ||||
| 	UIElement = 'NftImage', | ||||
| } | ||||
| function UI.NftImage:setParent() | ||||
| 	if self.image then | ||||
| function UI.NftImage:postInit() | ||||
| 	if self.image and not (self.ey or self.height) then | ||||
| 		self.height = self.image.height | ||||
| 	end | ||||
| 	if self.image and not self.width then | ||||
| 	if self.image and not (self.ex or self.width) then | ||||
| 		self.width = self.image.width | ||||
| 	end | ||||
| 	UI.Window.setParent(self) | ||||
| end | ||||
|  | ||||
| function UI.NftImage:draw() | ||||
| 	self:clear() | ||||
|  | ||||
| 	if self.image then | ||||
| 		-- due to blittle, the background and foreground transparent | ||||
| 		-- color is the same as the background color | ||||
| @@ -25,8 +26,6 @@ function UI.NftImage:draw() | ||||
| 				self:write(x, y, self.image.text[y][x], self.image.bg[y][x], self.image.fg[y][x] or bg) | ||||
| 			end | ||||
| 		end | ||||
| 	else | ||||
| 		self:clear() | ||||
| 	end | ||||
| end | ||||
|  | ||||
|   | ||||
| @@ -43,32 +43,34 @@ function UI.Notification:cancel() | ||||
| 		self.timer = nil | ||||
| 	end | ||||
|  | ||||
| 	if self.canvas then | ||||
| 		self.enabled = false | ||||
| 		self.canvas:removeLayer() | ||||
| 		self.canvas = nil | ||||
| 	end | ||||
| 	self:disable() | ||||
| end | ||||
|  | ||||
| function UI.Notification:display(value, timeout) | ||||
| 	self:cancel() | ||||
| 	self.enabled = true | ||||
| 	local lines = Util.wordWrap(value, self.width - 3) | ||||
|  | ||||
| 	self.enabled = true | ||||
| 	self.height = #lines | ||||
|  | ||||
| 	if self.anchor == 'bottom' then | ||||
| 		self.y = self.parent.height - self.height + 1 | ||||
| 		self.canvas = self:addLayer(self.backgroundColor, self.textColor) | ||||
| 		self:addTransition('expandUp', { ticks = self.height }) | ||||
| 	else | ||||
| 		self.canvas = self:addLayer(self.backgroundColor, self.textColor) | ||||
| 		self.y = 1 | ||||
| 	end | ||||
| 	self.canvas:setVisible(true) | ||||
|  | ||||
| 	self:reposition(self.x, self.y, self.width, self.height) | ||||
| 	self:raise() | ||||
| 	self:clear() | ||||
| 	for k,v in pairs(lines) do | ||||
| 		self:write(2, k, v) | ||||
| 	end | ||||
| 	self:write(self.width, 1, self.closeInd) | ||||
|  | ||||
| 	if self.timer then | ||||
| 		Event.off(self.timer) | ||||
| 		self.timer = nil | ||||
| 	end | ||||
|  | ||||
| 	timeout = timeout or self.timeout | ||||
| 	if timeout > 0 then | ||||
| @@ -77,7 +79,6 @@ function UI.Notification:display(value, timeout) | ||||
| 			self:sync() | ||||
| 		end) | ||||
| 	else | ||||
| 		self:write(self.width, 1, self.closeInd) | ||||
| 		self:sync() | ||||
| 	end | ||||
| end | ||||
| @@ -92,7 +93,7 @@ function UI.Notification:eventHandler(event) | ||||
| end | ||||
|  | ||||
| function UI.Notification.example() | ||||
| 	return UI.ActiveLayer { | ||||
| 	return UI.Window { | ||||
| 		notify1 = UI.Notification { | ||||
| 			anchor = 'top', | ||||
| 		}, | ||||
| @@ -111,7 +112,9 @@ function UI.Notification.example() | ||||
| 			if event.type == 'test_success' then | ||||
| 				self.notify1:success('Example text') | ||||
| 			elseif event.type == 'test_error' then | ||||
| 				self.notify2:error('Example text', 0) | ||||
| 				self.notify2:error([[Example text test test | ||||
| test test test test test | ||||
| test test test]], 0) | ||||
| 			end | ||||
| 		end, | ||||
| 	} | ||||
|   | ||||
| @@ -1,21 +1,9 @@ | ||||
| local Canvas = require('opus.ui.canvas') | ||||
| local class  = require('opus.class') | ||||
| local UI     = require('opus.ui') | ||||
| local Util   = require('opus.util') | ||||
|  | ||||
| local colors = _G.colors | ||||
|  | ||||
| -- need to add offsets to this test | ||||
| local function getPosition(element) | ||||
| 	local x, y = 1, 1 | ||||
| 	repeat | ||||
| 		x = element.x + x - 1 | ||||
| 		y = element.y + y - 1 | ||||
| 		element = element.parent | ||||
| 	until not element | ||||
| 	return x, y | ||||
| end | ||||
|  | ||||
| UI.Page = class(UI.Window) | ||||
| UI.Page.defaults = { | ||||
| 	UIElement = 'Page', | ||||
| @@ -26,21 +14,15 @@ UI.Page.defaults = { | ||||
| 		['shift-tab' ] = 'focus_prev', | ||||
| 		up = 'focus_prev', | ||||
| 	}, | ||||
| 	backgroundColor = colors.cyan, | ||||
| 	backgroundColor = UI.colors.primary, | ||||
| 	textColor = colors.white, | ||||
| } | ||||
| function UI.Page:postInit() | ||||
| 	self.parent = self.parent or UI.defaultDevice | ||||
| 	self.__target = self | ||||
| 	self.canvas = Canvas({ | ||||
| 		x = 1, y = 1, width = self.parent.width, height = self.parent.height, | ||||
| 		isColor = self.parent.isColor, | ||||
| 	}) | ||||
| 	self.canvas:clear(self.backgroundColor, self.textColor) | ||||
| end | ||||
|  | ||||
| function UI.Page:enable() | ||||
| 	self.canvas.visible = true | ||||
| 	UI.Window.enable(self) | ||||
|  | ||||
| 	if not self.focused or not self.focused.enabled then | ||||
| @@ -49,12 +31,12 @@ function UI.Page:enable() | ||||
| end | ||||
|  | ||||
| function UI.Page:disable() | ||||
| 	self.canvas.visible = false | ||||
| 	UI.Window.disable(self) | ||||
| end | ||||
|  | ||||
| function UI.Page:sync() | ||||
| 	if self.enabled then | ||||
| 		self:checkFocus() | ||||
| 		self.parent:sync() | ||||
| 	end | ||||
| end | ||||
| @@ -73,22 +55,24 @@ function UI.Page:pointToChild(x, y) | ||||
| 	if self.__target == self then | ||||
| 		return UI.Window.pointToChild(self, x, y) | ||||
| 	end | ||||
| 	x = x + self.offx - self.x + 1 | ||||
| 	y = y + self.offy - self.y + 1 | ||||
| --[[ | ||||
| 	-- this is supposed to fix when there are multiple sub canvases | ||||
| 	local absX, absY = getPosition(self.__target) | ||||
| 	if self.__target.canvas then | ||||
| 		x = x - (self.__target.canvas.x - self.__target.x) | ||||
| 		y = y - (self.__target.canvas.y - self.__target.y) | ||||
| 		_syslog({'raw', self.__target.canvas.y, self.__target.y}) | ||||
|  | ||||
| 	-- need to add offsets to this test | ||||
| 	local function getPosition(element) | ||||
| 		local x, y = 1, 1 | ||||
| 		repeat | ||||
| 			x = element.x + x - 1 | ||||
| 			y = element.y + y - 1 | ||||
| 			element = element.parent | ||||
| 		until not element | ||||
| 		return x, y | ||||
| 	end | ||||
| 	]] | ||||
| 	return self.__target:pointToChild(x, y) | ||||
|  | ||||
| 	local absX, absY = getPosition(self.__target) | ||||
| 	return self.__target:pointToChild(x - absX + self.__target.x, y - absY + self.__target.y) | ||||
| end | ||||
|  | ||||
| function UI.Page:getFocusables() | ||||
| 	if self.__target == self or self.__target.pageType ~= 'modal' then | ||||
| 	if self.__target == self or not self.__target.modal then | ||||
| 		return UI.Window.getFocusables(self) | ||||
| 	end | ||||
| 	return self.__target:getFocusables() | ||||
| @@ -149,12 +133,25 @@ function UI.Page:setFocus(child) | ||||
| 	if not child.focused then | ||||
| 		child.focused = true | ||||
| 		child:emit({ type = 'focus_change', focused = child }) | ||||
| 		--self:emit({ type = 'focus_change', focused = child }) | ||||
| 	end | ||||
|  | ||||
| 	child:focus() | ||||
| end | ||||
|  | ||||
| function UI.Page:checkFocus() | ||||
| 	if not self.focused or not self.focused.enabled then | ||||
| 		local el = self.__target | ||||
| 		while el do | ||||
| 			local focusables = el:getFocusables() | ||||
| 			if focusables[1] then | ||||
| 				self:setFocus(focusables[1]) | ||||
| 				break | ||||
| 			end | ||||
| 			el = el.parent | ||||
| 		end | ||||
| 	end | ||||
| end | ||||
|  | ||||
| function UI.Page:eventHandler(event) | ||||
| 	if self.focused then | ||||
| 		if event.type == 'focus_next' then | ||||
|   | ||||
| @@ -28,12 +28,11 @@ function UI.ProgressBar:draw() | ||||
| end | ||||
|  | ||||
| function UI.ProgressBar.example() | ||||
| 	local Event = require('opus.event') | ||||
| 	return UI.ProgressBar { | ||||
| 		x = 2, ex = -2, y = 2, | ||||
| 		focus = function() end, | ||||
| 		enable = function(self) | ||||
| 			Event.onInterval(.25, function() | ||||
| 			require('opus.event').onInterval(.25, function() | ||||
| 				self.value = self.value == 100 and 0 or self.value + 5 | ||||
| 				self:draw() | ||||
| 				self:sync() | ||||
|   | ||||
| @@ -17,7 +17,10 @@ UI.ScrollBar.defaults = { | ||||
| 	ey = -1, | ||||
| } | ||||
| function UI.ScrollBar:draw() | ||||
| 	local view = self.parent:getViewArea() | ||||
| 	local parent = self.target or self.parent --self:find(self.target) | ||||
| 	local view = parent:getViewArea() | ||||
|  | ||||
| 	self:clear() | ||||
|  | ||||
| 	if view.totalHeight > view.height then | ||||
| 		local maxScroll = view.totalHeight - view.height | ||||
| @@ -27,7 +30,7 @@ function UI.ScrollBar:draw() | ||||
|  | ||||
| 		local row = view.y | ||||
| 		if not view.static then  -- does the container scroll ? | ||||
| 			self.height = view.totalHeight | ||||
| 			self:reposition(self.x, self.y, self.width, view.totalHeight) | ||||
| 		end | ||||
|  | ||||
| 		for i = 1, view.height - 2 do | ||||
| @@ -56,16 +59,17 @@ end | ||||
| function UI.ScrollBar:eventHandler(event) | ||||
| 	if event.type == 'mouse_click' or event.type == 'mouse_doubleclick' then | ||||
| 		if event.x == 1 then | ||||
| 			local view = self.parent:getViewArea() | ||||
| 			local parent = self.target or self.parent --self:find(self.target) | ||||
| 			local view = parent:getViewArea() | ||||
| 			if view.totalHeight > view.height then | ||||
| 				if event.y == view.y then | ||||
| 					self:emit({ type = 'scroll_up'}) | ||||
| 					parent:emit({ type = 'scroll_up'}) | ||||
| 				elseif event.y == view.y + view.height - 1 then | ||||
| 					self:emit({ type = 'scroll_down'}) | ||||
| 					parent:emit({ type = 'scroll_down'}) | ||||
| 				else | ||||
| 					local percent = (event.y - view.y) / (view.height - 2) | ||||
| 					local y = math.floor((view.totalHeight - view.height) * percent) | ||||
| 					self:emit({ type = 'scroll_to', offset = y }) | ||||
| 					parent :emit({ type = 'scroll_to', offset = y }) | ||||
| 				end | ||||
| 			end | ||||
| 			return true | ||||
|   | ||||
| @@ -57,3 +57,21 @@ function UI.ScrollingGrid:setIndex(index) | ||||
| 	end | ||||
| 	UI.Grid.setIndex(self, index) | ||||
| end | ||||
|  | ||||
| function UI.ScrollingGrid.example() | ||||
| 	local values = { } | ||||
| 	for i = 1, 20 do | ||||
| 		table.insert(values, { key = 'key' .. i, value = 'value' .. i }) | ||||
| 	end | ||||
| 	return UI.ScrollingGrid { | ||||
| 		values = values, | ||||
| 		sortColumn = 'key', | ||||
| 		columns = { | ||||
| 			{ heading = 'key', key = 'key' }, | ||||
| 			{ heading = 'value', key = 'value' }, | ||||
| 		}, | ||||
| 		accelerators = { | ||||
| 			grid_select = 'custom_select', | ||||
| 		} | ||||
| 	} | ||||
| end | ||||
| @@ -4,17 +4,9 @@ local UI    = require('opus.ui') | ||||
| UI.SlideOut = class(UI.Window) | ||||
| UI.SlideOut.defaults = { | ||||
| 	UIElement = 'SlideOut', | ||||
| 	pageType = 'modal', | ||||
| 	transitionHint = 'expandUp', | ||||
| 	modal = true, | ||||
| } | ||||
| function UI.SlideOut:layout() | ||||
| 	UI.Window.layout(self) | ||||
| 	if not self.canvas then | ||||
| 		self.canvas = self:addLayer() | ||||
| 	else | ||||
| 		self.canvas:resize(self.width, self.height) | ||||
| 	end | ||||
| end | ||||
|  | ||||
| function UI.SlideOut:enable() | ||||
| end | ||||
|  | ||||
| @@ -27,24 +19,20 @@ function UI.SlideOut:toggle() | ||||
| end | ||||
|  | ||||
| function UI.SlideOut:show(...) | ||||
| 	self:addTransition('expandUp') | ||||
| 	self.canvas:raise() | ||||
| 	self.canvas:setVisible(true) | ||||
| 	UI.Window.enable(self, ...) | ||||
| 	self:draw() | ||||
| 	self:capture(self) | ||||
| 	self:focusFirst() | ||||
| end | ||||
|  | ||||
| function UI.SlideOut:disable() | ||||
| 	self.canvas:setVisible(false) | ||||
| 	UI.Window.disable(self) | ||||
| end | ||||
|  | ||||
| function UI.SlideOut:hide() | ||||
| 	self:disable() | ||||
| 	self:release(self) | ||||
| 	self:refocus() | ||||
| end | ||||
|  | ||||
| function UI.SlideOut:draw() | ||||
| 	if not self.noFill then | ||||
| 		self:fillArea(1, 1, self.width, self.height, string.rep('\127', self.width), colors.black, colors.gray) | ||||
| 	end | ||||
| 	self:drawChildren() | ||||
| end | ||||
|  | ||||
| function UI.SlideOut:eventHandler(event) | ||||
| @@ -59,24 +47,27 @@ function UI.SlideOut:eventHandler(event) | ||||
| end | ||||
|  | ||||
| function UI.SlideOut.example() | ||||
| 	-- for the transistion to work properly, the parent must have a canvas | ||||
| 	return UI.ActiveLayer { | ||||
| 		y = 2, | ||||
| 	return UI.Window { | ||||
| 		y = 3, | ||||
| 		backgroundColor = 2048, | ||||
| 		button = UI.Button { | ||||
| 			x = 2, y = 5, | ||||
| 			text = 'show', | ||||
| 		}, | ||||
| 		slideOut = UI.SlideOut { | ||||
| 			backgroundColor = _G.colors.yellow, | ||||
| 			y = -4, height = 4, x = 3, ex = -3, | ||||
| 			backgroundColor = 16, | ||||
| 			y = -7, height = 4, x = 3, ex = -3, | ||||
| 			titleBar = UI.TitleBar { | ||||
| 				title = 'test', | ||||
| 			}, | ||||
| 			button = UI.Button { | ||||
| 				x = 2, y = 2, | ||||
| 				text = 'hide', | ||||
| 				--visualize = true, | ||||
| 			}, | ||||
| 		}, | ||||
| 		eventHandler = function (self, event) | ||||
| 			if event.type == 'button_press' then | ||||
| 				self.slideOut.canvas.xxx = true | ||||
| 				self.slideOut:toggle() | ||||
| 			end | ||||
| 		end, | ||||
|   | ||||
| @@ -57,8 +57,16 @@ end | ||||
|  | ||||
| function UI.Slider:eventHandler(event) | ||||
| 	if event.type == "mouse_down" or event.type == "mouse_drag" then | ||||
|  | ||||
| 		local pos = event.x - 1 | ||||
| 		if event.type == 'mouse_down' then | ||||
| 			self.anchor = event.x - 1 | ||||
| 		else | ||||
| 			pos = self.anchor + event.dx | ||||
| 		end | ||||
|  | ||||
| 		local range = self.max - self.min | ||||
| 		local i = (event.x - 1) / (self.width - 1) | ||||
| 		local i = pos / (self.width - 1) | ||||
| 		self.value = self.min + (i * range) | ||||
| 		self:emit({ type = self.event, value = self.value, element = self }) | ||||
| 		self:draw() | ||||
|   | ||||
| @@ -63,7 +63,7 @@ end | ||||
|  | ||||
| function UI.StatusBar:timedStatus(status, timeout) | ||||
| 	self:write(2, 1, Util.widthify(status, self.width-2), self.backgroundColor) | ||||
| 	Event.on(timeout or 3, function() | ||||
| 	Event.onTimeout(timeout or 3, function() | ||||
| 		if self.enabled then | ||||
| 			self:draw() | ||||
| 			self:sync() | ||||
|   | ||||
| @@ -1,9 +1,16 @@ | ||||
| local class = require('opus.class') | ||||
| local UI    = require('opus.ui') | ||||
|  | ||||
| UI.Tab = class(UI.ActiveLayer) | ||||
| UI.Tab = class(UI.Window) | ||||
| UI.Tab.defaults = { | ||||
| 	UIElement = 'Tab', | ||||
| 	tabTitle = 'tab', | ||||
| 	y = 2, | ||||
| } | ||||
|  | ||||
| function UI.Tab:draw() | ||||
| 	if not self.noFill then | ||||
| 		self:fillArea(1, 1, self.width, self.height, string.rep('\127', self.width), colors.black, colors.gray) | ||||
| 	end | ||||
| 	self:drawChildren() | ||||
| end | ||||
|   | ||||
| @@ -2,13 +2,14 @@ local class = require('opus.class') | ||||
| local UI    = require('opus.ui') | ||||
| local Util  = require('opus.util') | ||||
|  | ||||
| local colors = _G.colors | ||||
|  | ||||
| UI.TabBar = class(UI.MenuBar) | ||||
| UI.TabBar.defaults = { | ||||
| 	UIElement = 'TabBar', | ||||
| 	buttonClass = 'TabBarMenuItem', | ||||
| } | ||||
| UI.TabBar.inherits = { | ||||
| 	selectedBackgroundColor = 'backgroundColor', | ||||
| 	selectedBackgroundColor = UI.colors.secondary, | ||||
| 	unselectedBackgroundColor = colors.lightGray, | ||||
| } | ||||
| function UI.TabBar:enable() | ||||
| 	UI.MenuBar.enable(self) | ||||
|   | ||||
| @@ -1,27 +1,18 @@ | ||||
| local class = require('opus.class') | ||||
| local UI    = require('opus.ui') | ||||
|  | ||||
| local colors = _G.colors | ||||
|  | ||||
| UI.TabBarMenuItem = class(UI.Button) | ||||
| UI.TabBarMenuItem.defaults = { | ||||
| 	UIElement = 'TabBarMenuItem', | ||||
| 	event = 'tab_select', | ||||
| 	textColor = colors.black, | ||||
| 	selectedBackgroundColor = colors.cyan, | ||||
| 	unselectedBackgroundColor = colors.lightGray, | ||||
| 	backgroundColor = colors.lightGray, | ||||
| } | ||||
| UI.TabBarMenuItem.inherits = { | ||||
| 	selectedBackgroundColor = 'selectedBackgroundColor', | ||||
| } | ||||
| function UI.TabBarMenuItem:draw() | ||||
| 	if self.selected then | ||||
| 		self.backgroundColor = self.selectedBackgroundColor | ||||
| 		self.backgroundFocusColor = self.selectedBackgroundColor | ||||
| 		self.backgroundColor = self:getProperty('selectedBackgroundColor') | ||||
| 		self.backgroundFocusColor = self.backgroundColor | ||||
| 	else | ||||
| 		self.backgroundColor = self.unselectedBackgroundColor | ||||
| 		self.backgroundFocusColor = self.unselectedBackgroundColor | ||||
| 		self.backgroundColor = self:getProperty('unselectedBackgroundColor') | ||||
| 		self.backgroundFocusColor = self.backgroundColor | ||||
| 	end | ||||
| 	UI.Button.draw(self) | ||||
| end | ||||
|   | ||||
| @@ -56,12 +56,12 @@ end | ||||
|  | ||||
| function UI.Tabs:enable() | ||||
| 	self.enabled = true | ||||
| 	self.transitionHint = nil | ||||
| 	self.tabBar:enable() | ||||
|  | ||||
| 	local menuItem = Util.find(self.tabBar.children, 'selected', true) | ||||
|  | ||||
| 	for _,child in pairs(self.children or { }) do | ||||
| 	for child in self:eachChild() do | ||||
| 		child.transitionHint = nil | ||||
| 		if child.uid == menuItem.tabUid then | ||||
| 			child:enable() | ||||
| 			self:emit({ type = 'tab_activate', activated = child }) | ||||
| @@ -74,14 +74,11 @@ end | ||||
| function UI.Tabs:eventHandler(event) | ||||
| 	if event.type == 'tab_change' then | ||||
| 		local tab = self:find(event.tab.tabUid) | ||||
| 		if event.current > event.last then | ||||
| 			self.transitionHint = 'slideLeft' | ||||
| 		else | ||||
| 			self.transitionHint = 'slideRight' | ||||
| 		end | ||||
| 		local hint = event.current > event.last and 'slideLeft' or 'slideRight' | ||||
|  | ||||
| 		for _,child in pairs(self.children) do | ||||
| 		for child in self:eachChild() do | ||||
| 			if child.uid == event.tab.tabUid then | ||||
| 				child.transitionHint = hint | ||||
| 				child:enable() | ||||
| 			elseif child.tabTitle then | ||||
| 				child:disable() | ||||
| @@ -89,6 +86,7 @@ function UI.Tabs:eventHandler(event) | ||||
| 		end | ||||
| 		self:emit({ type = 'tab_activate', activated = tab }) | ||||
| 		tab:draw() | ||||
| 		return true | ||||
| 	end | ||||
| end | ||||
|  | ||||
| @@ -102,7 +100,18 @@ function UI.Tabs.example() | ||||
| 		tab2 = UI.Tab { | ||||
| 			index = 2, | ||||
| 			tabTitle = 'tab2', | ||||
| 			button = UI.Button { y = 3 }, | ||||
| 			subtabs = UI.Tabs { | ||||
| 				x = 3, y = 2, ex = -3, ey = -2, | ||||
| 				tab1 = UI.Tab { | ||||
| 					index = 1, | ||||
| 					tabTitle = 'tab4', | ||||
| 					entry = UI.TextEntry { y = 3, shadowText = 'text' }, | ||||
| 				}, | ||||
| 				tab3 = UI.Tab { | ||||
| 					index = 2, | ||||
| 					tabTitle = 'tab5', | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		tab3 = UI.Tab { | ||||
| 			index = 3, | ||||
|   | ||||
| @@ -6,11 +6,8 @@ UI.TextArea.defaults = { | ||||
| 	UIElement = 'TextArea', | ||||
| 	marginRight = 2, | ||||
| 	value = '', | ||||
| 	showScrollBar = true, | ||||
| } | ||||
| function UI.TextArea:postInit() | ||||
| 	self.scrollBar = UI.ScrollBar() | ||||
| end | ||||
|  | ||||
| function UI.TextArea:setText(text) | ||||
| 	self:reset() | ||||
| 	self.value = text | ||||
| @@ -23,19 +20,28 @@ end | ||||
|  | ||||
| function UI.TextArea:draw() | ||||
| 	self:clear() | ||||
| --  self:setCursorPos(1, 1) | ||||
| 	self.cursorX, self.cursorY = 1, 1 | ||||
| 	self:print(self.value) | ||||
|  | ||||
| 	for _,child in pairs(self.children) do | ||||
| 		if child.enabled then | ||||
| 			child:draw() | ||||
| 		end | ||||
| 	end | ||||
| 	self:drawChildren() | ||||
| end | ||||
|  | ||||
| function UI.TextArea.example() | ||||
| 	return UI.TextArea { | ||||
| 	return UI.Window { | ||||
| 		backgroundColor = 2048, | ||||
| 		t1 = UI.TextArea { | ||||
| 			ey = 3, | ||||
| 			value = 'sample text\nabc' | ||||
| 		}, | ||||
| 		t2 = UI.TextArea { | ||||
| 			y = 5, | ||||
| 			value = [[1 | ||||
| 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | ||||
| 3 | ||||
| 4 | ||||
| 5 | ||||
| 6 | ||||
| 7 | ||||
| 8]] | ||||
| 		} | ||||
| 	} | ||||
| end | ||||
| @@ -19,7 +19,6 @@ UI.TextEntry = class(UI.Window) | ||||
| UI.TextEntry.docs = { } | ||||
| UI.TextEntry.defaults = { | ||||
| 	UIElement = 'TextEntry', | ||||
| 	--value = '', | ||||
| 	shadowText = '', | ||||
| 	focused = false, | ||||
| 	textColor = colors.white, | ||||
|   | ||||
| @@ -20,25 +20,16 @@ UI.Throttle.defaults = { | ||||
| 		'  //)    (O ). @  \\-d )      (@ ' | ||||
| 	} | ||||
| } | ||||
| function UI.Throttle:setParent() | ||||
| function UI.Throttle:layout() | ||||
| 	self.x = math.ceil((self.parent.width - self.width) / 2) | ||||
| 	self.y = math.ceil((self.parent.height - self.height) / 2) | ||||
| 	UI.Window.setParent(self) | ||||
| 	self:reposition(self.x, self.y, self.width, self.height) | ||||
| end | ||||
|  | ||||
| function UI.Throttle:enable() | ||||
| 	self.c = os.clock() | ||||
| 	self.enabled = false | ||||
| end | ||||
|  | ||||
| function UI.Throttle:disable() | ||||
| 	if self.canvas then | ||||
| 		self.enabled = false | ||||
| 		self.canvas:removeLayer() | ||||
| 		self.canvas = nil | ||||
| 	self.ctr = 0 | ||||
| end | ||||
| end | ||||
|  | ||||
| function UI.Throttle:update() | ||||
| 	local cc = os.clock() | ||||
| @@ -46,11 +37,7 @@ function UI.Throttle:update() | ||||
| 		os.sleep(0) | ||||
| 		self.c = os.clock() | ||||
| 		self.enabled = true | ||||
| 		if not self.canvas then | ||||
| 			self.canvas = self:addLayer(self.backgroundColor, self.borderColor) | ||||
| 			self.canvas:setVisible(true) | ||||
| 		self:clear(self.borderColor) | ||||
| 		end | ||||
| 		local image = self.image[self.ctr + 1] | ||||
| 		local width = self.width - 2 | ||||
| 		for i = 0, #self.image do | ||||
| @@ -63,3 +50,25 @@ function UI.Throttle:update() | ||||
| 		self:sync() | ||||
| 	end | ||||
| end | ||||
|  | ||||
| function UI.Throttle.example() | ||||
| 	return UI.Window { | ||||
| 		button1 = UI.Button { | ||||
| 			x = 2, y = 2, | ||||
| 			text = 'Test', | ||||
| 		}, | ||||
| 		throttle = UI.Throttle { | ||||
| 			textColor = colors.yellow, | ||||
| 			borderColor = colors.green, | ||||
| 		}, | ||||
| 		eventHandler = function (self, event) | ||||
| 			if event.type == 'button_press' then | ||||
| 				for _ = 1, 40 do | ||||
| 					self.throttle:update() | ||||
| 					os.sleep(.05) | ||||
| 				end | ||||
| 				self.throttle:disable() | ||||
| 			end | ||||
| 		end, | ||||
| 	} | ||||
| end | ||||
|   | ||||
| @@ -38,8 +38,6 @@ UI.TitleBar = class(UI.Window) | ||||
| UI.TitleBar.defaults = { | ||||
| 	UIElement = 'TitleBar', | ||||
| 	height = 1, | ||||
| 	textColor = colors.white, | ||||
| 	backgroundColor = colors.cyan, | ||||
| 	title = '', | ||||
| 	frameChar = UI.extChars and '\140' or '-', | ||||
| 	closeInd = UI.extChars and '\215' or '*', | ||||
| @@ -69,5 +67,73 @@ function UI.TitleBar:eventHandler(event) | ||||
| 			end | ||||
| 			return true | ||||
| 		end | ||||
|  | ||||
| 	elseif event.type == 'mouse_down' then | ||||
| 		self.anchor = { x = event.x, y = event.y, ox = self.parent.x, oy = self.parent.y, h = self.parent.height } | ||||
|  | ||||
| 	elseif event.type == 'mouse_drag' then | ||||
| 		if self.expand == 'height' then | ||||
| 			local d = event.dy | ||||
| 			if self.anchor.h - d > 0 and self.anchor.oy + d > 0 then | ||||
| 				self.parent:reposition(self.parent.x, self.anchor.oy + event.dy, self.width, self.anchor.h - d) | ||||
| 			end | ||||
|  | ||||
| 		else --if self.moveable then | ||||
| 			local d = event.dy | ||||
| 			if self.anchor.oy + d > 0 and self.anchor.oy + d <= self.parent.parent.height then | ||||
| 				self.parent:move(self.anchor.ox + event.dx, self.anchor.oy + event.dy) | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
| end | ||||
|  | ||||
| function UI.TitleBar.example() | ||||
| 	return UI.Window { | ||||
| 		win1 = UI.Window { | ||||
| 			x = 9, y = 2, ex = -7, ey = -3, | ||||
| 			backgroundColor = colors.green, | ||||
| 			titleBar = UI.TitleBar { | ||||
| 				title = 'test',  moveable = true, | ||||
| 			}, | ||||
| 			button1 = UI.Button { | ||||
| 				x = 2, y = 3, | ||||
| 				text = 'Press', | ||||
| 			}, | ||||
| 			focus = function (self) | ||||
| 				self:raise() | ||||
| 			end, | ||||
| 		}, | ||||
| 		win2 = UI.Window { | ||||
| 			x = 7, y = 3, ex = -9, ey = -2, | ||||
| 			backgroundColor = colors.orange, | ||||
| 			titleBar = UI.TitleBar { | ||||
| 				title = 'test', moveable = true, | ||||
| 			}, | ||||
| 			button1 = UI.Button { | ||||
| 				x = 2, y = 3, | ||||
| 				text = 'Press', | ||||
| 			}, | ||||
| 			focus = function (self) | ||||
| 				self:raise() | ||||
| 			end, | ||||
| 		}, | ||||
| 		draw = function(self, isBG) | ||||
| 			for i = 1, self.height do | ||||
| 				self:write(1, i, self.filler or '') | ||||
| 			end | ||||
| 			if not isBG then | ||||
| 				for _,v in pairs(self.children) do | ||||
| 					v:draw() | ||||
| 				end | ||||
| 			end | ||||
| 		end, | ||||
| 		enable = function (self) | ||||
| 			require('opus.event').onInterval(.5, function() | ||||
| 				self.filler = string.rep(string.char(math.random(33, 126)), self.width) | ||||
| 				self:draw(true) | ||||
| 				self:sync() | ||||
| 			end) | ||||
| 			UI.Window.enable(self) | ||||
| 		end | ||||
| 	} | ||||
| end | ||||
|   | ||||
| @@ -18,12 +18,11 @@ function UI.VerticalMeter:draw() | ||||
| end | ||||
|  | ||||
| function UI.VerticalMeter.example() | ||||
| 	local Event = require('opus.event') | ||||
| 	return UI.VerticalMeter { | ||||
| 		x = 2, width = 3, y = 2, ey = -2, | ||||
| 		focus = function() end, | ||||
| 		enable = function(self) | ||||
| 			Event.onInterval(.25, function() | ||||
| 			require('opus.event').onInterval(.25, function() | ||||
| 				self.value = self.value == 100 and 0 or self.value + 5 | ||||
| 				self:draw() | ||||
| 				self:sync() | ||||
|   | ||||
| @@ -1,16 +1,15 @@ | ||||
| local class = require('opus.class') | ||||
| local UI    = require('opus.ui') | ||||
|  | ||||
| local colors = _G.colors | ||||
|  | ||||
| UI.Viewport = class(UI.Window) | ||||
| UI.Viewport.defaults = { | ||||
| 	UIElement = 'Viewport', | ||||
| 	backgroundColor = colors.cyan, | ||||
| 	accelerators = { | ||||
| 		down            = 'scroll_down', | ||||
| 		up              = 'scroll_up', | ||||
| 		home            = 'scroll_top', | ||||
| 		left            = 'scroll_left', | ||||
| 		right           = 'scroll_right', | ||||
| 		[ 'end' ]       = 'scroll_bottom', | ||||
| 		pageUp          = 'scroll_pageUp', | ||||
| 		[ 'control-b' ] = 'scroll_pageUp', | ||||
| @@ -18,53 +17,53 @@ UI.Viewport.defaults = { | ||||
| 		[ 'control-f' ] = 'scroll_pageDown', | ||||
| 	}, | ||||
| } | ||||
| function UI.Viewport:layout() | ||||
| 	UI.Window.layout(self) | ||||
| 	if not self.canvas then | ||||
| 		self.canvas = self:addLayer() | ||||
| 	else | ||||
| 		self.canvas:resize(self.width, self.height) | ||||
| function UI.Viewport:postInit() | ||||
| 	if self.showScrollBar then | ||||
| 		self.scrollBar = UI.ScrollBar() | ||||
| 	end | ||||
| end | ||||
|  | ||||
| function UI.Viewport:enable() | ||||
| 	UI.Window.enable(self) | ||||
| 	self.canvas:setVisible(true) | ||||
| end | ||||
|  | ||||
| function UI.Viewport:disable() | ||||
| 	UI.Window.disable(self) | ||||
| 	self.canvas:setVisible(false) | ||||
| end | ||||
|  | ||||
| function UI.Viewport:setScrollPosition(offset) | ||||
| 	local oldOffset = self.offy | ||||
| 	self.offy = math.max(offset, 0) | ||||
| 	self.offy = math.min(self.offy, math.max(#self.canvas.lines, self.height) - self.height) | ||||
| 	if self.offy ~= oldOffset then | ||||
| function UI.Viewport:setScrollPosition(offy, offx) -- argh - reverse | ||||
| 	local oldOffy = self.offy | ||||
| 	self.offy = math.max(offy, 0) | ||||
| 	self.offy = math.min(self.offy, math.max(#self.lines, self.height) - self.height) | ||||
| 	if self.offy ~= oldOffy then | ||||
| 		if self.scrollBar then | ||||
| 			self.scrollBar:draw() | ||||
| 		end | ||||
| 		self.canvas.offy = offset | ||||
| 		self.canvas:dirty() | ||||
| 		self.offy = offy | ||||
| 		self:dirty(true) | ||||
| 	end | ||||
|  | ||||
| 	local oldOffx = self.offx | ||||
| 	self.offx = math.max(offx or 0, 0) | ||||
| 	self.offx = math.min(self.offx, math.max(#self.lines[1], self.width) - self.width) | ||||
| 	if self.offx ~= oldOffx then | ||||
| 		if self.scrollBar then | ||||
| 			--self.scrollBar:draw() | ||||
| 		end | ||||
| 		self.offx = offx or 0 | ||||
| 		self:dirty(true) | ||||
| 	end | ||||
| end | ||||
|  | ||||
| function UI.Viewport:write(x, y, text, bg, tc) | ||||
| 	if y > #self.canvas.lines then | ||||
| 		for i = #self.canvas.lines, y do | ||||
| 			self.canvas.lines[i + 1] = { } | ||||
| 			self.canvas:clearLine(i + 1, self.backgroundColor, self.textColor) | ||||
| 		end | ||||
| 	if y > #self.lines then | ||||
| 		self:resizeBuffer(self.width, y) | ||||
| 	end | ||||
| 	return UI.Window.write(self, x, y, text, bg, tc) | ||||
| end | ||||
|  | ||||
| function UI.Viewport:setViewHeight(h) | ||||
| 	if h > #self.lines then | ||||
| 		self:resizeBuffer(self.width, h) | ||||
| 	end | ||||
| end | ||||
|  | ||||
| function UI.Viewport:reset() | ||||
| 	self.offy = 0 | ||||
| 	self.canvas.offy = 0 | ||||
| 	for i = self.height + 1, #self.canvas.lines do | ||||
| 		self.canvas.lines[i] = nil | ||||
| 	for i = self.height + 1, #self.lines do | ||||
| 		self.lines[i] = nil | ||||
| 	end | ||||
| end | ||||
|  | ||||
| @@ -72,26 +71,30 @@ function UI.Viewport:getViewArea() | ||||
| 	return { | ||||
| 		y           = (self.offy or 0) + 1, | ||||
| 		height      = self.height, | ||||
| 		totalHeight = #self.canvas.lines, | ||||
| 		totalHeight = #self.lines, | ||||
| 		offsetY     = self.offy or 0, | ||||
| 	} | ||||
| end | ||||
|  | ||||
| function UI.Viewport:eventHandler(event) | ||||
| 	if event.type == 'scroll_down' then | ||||
| 		self:setScrollPosition(self.offy + 1) | ||||
| 		self:setScrollPosition(self.offy + 1, self.offx) | ||||
| 	elseif event.type == 'scroll_up' then | ||||
| 		self:setScrollPosition(self.offy - 1) | ||||
| 		self:setScrollPosition(self.offy - 1, self.offx) | ||||
| 	elseif event.type == 'scroll_left' then | ||||
| 		self:setScrollPosition(self.offy, self.offx - 1) | ||||
| 	elseif event.type == 'scroll_right' then | ||||
| 		self:setScrollPosition(self.offy, self.offx + 1) | ||||
| 	elseif event.type == 'scroll_top' then | ||||
| 		self:setScrollPosition(0) | ||||
| 		self:setScrollPosition(0, 0) | ||||
| 	elseif event.type == 'scroll_bottom' then | ||||
| 		self:setScrollPosition(10000000) | ||||
| 		self:setScrollPosition(10000000, 0) | ||||
| 	elseif event.type == 'scroll_pageUp' then | ||||
| 		self:setScrollPosition(self.offy - self.height) | ||||
| 		self:setScrollPosition(self.offy - self.height, self.offx) | ||||
| 	elseif event.type == 'scroll_pageDown' then | ||||
| 		self:setScrollPosition(self.offy + self.height) | ||||
| 		self:setScrollPosition(self.offy + self.height, self.offx) | ||||
| 	elseif event.type == 'scroll_to' then | ||||
| 		self:setScrollPosition(event.offset) | ||||
| 		self:setScrollPosition(event.offset, 0) | ||||
| 	else | ||||
| 		return false | ||||
| 	end | ||||
|   | ||||
| @@ -25,9 +25,6 @@ function UI.Wizard:postInit() | ||||
| 	} | ||||
|  | ||||
| 	Util.merge(self, self.pages) | ||||
| 	--for _, child in pairs(self.pages) do | ||||
| 	--	child.ey = -2 | ||||
| 	--end | ||||
| end | ||||
|  | ||||
| function UI.Wizard:add(pages) | ||||
| @@ -50,9 +47,8 @@ end | ||||
| function UI.Wizard:enable(...) | ||||
| 	self.enabled = true | ||||
| 	self.index = 1 | ||||
| 	self.transitionHint = nil | ||||
| 	local initial = self:getPage(1) | ||||
| 	for _,child in pairs(self.children) do | ||||
| 	for child in self:eachChild() do | ||||
| 		if child == initial or not child.index then | ||||
| 			child:enable(...) | ||||
| 		else | ||||
| @@ -93,12 +89,13 @@ function UI.Wizard:eventHandler(event) | ||||
| 	elseif event.type == 'enable_view' then | ||||
| 		local current = event.next or event.prev | ||||
| 		if not current then error('property "index" is required on wizard pages') end | ||||
| 		local hint | ||||
|  | ||||
| 		if event.current then | ||||
| 			if event.next then | ||||
| 				self.transitionHint = 'slideLeft' | ||||
| 				hint = 'slideLeft' | ||||
| 			elseif event.prev then | ||||
| 				self.transitionHint = 'slideRight' | ||||
| 				hint = 'slideRight' | ||||
| 			end | ||||
| 			event.current:disable() | ||||
| 		end | ||||
| @@ -117,6 +114,7 @@ function UI.Wizard:eventHandler(event) | ||||
| 			self.nextButton.event = 'wizard_complete' | ||||
| 		end | ||||
| 		-- a new current view | ||||
| 		current.transitionHint = hint | ||||
| 		current:enable() | ||||
| 		current:emit({ type = 'view_enabled', view = current }) | ||||
| 		self:draw() | ||||
|   | ||||
| @@ -1,11 +1,8 @@ | ||||
| local class = require('opus.class') | ||||
| local UI    = require('opus.ui') | ||||
|  | ||||
| local colors = _G.colors | ||||
|  | ||||
| UI.WizardPage = class(UI.ActiveLayer) | ||||
| UI.WizardPage = class(UI.Window) | ||||
| UI.WizardPage.defaults = { | ||||
| 	UIElement = 'WizardPage', | ||||
| 	backgroundColor = colors.cyan, | ||||
| 	ey = -2, | ||||
| } | ||||
|   | ||||
| @@ -13,7 +13,7 @@ function Transition.slideLeft(args) | ||||
| 	return function() | ||||
| 		local finished = tween:update(1) | ||||
| 		args.canvas:move(math.floor(pos.x), args.canvas.y) | ||||
| 		args.canvas:dirty() | ||||
| 		args.canvas:dirty(true) | ||||
| 		return not finished | ||||
| 	end | ||||
| end | ||||
| @@ -29,7 +29,7 @@ function Transition.slideRight(args) | ||||
| 	return function() | ||||
| 		local finished = tween:update(1) | ||||
| 		args.canvas:move(math.floor(pos.x), args.canvas.y) | ||||
| 		args.canvas:dirty() | ||||
| 		args.canvas:dirty(true) | ||||
| 		return not finished | ||||
| 	end | ||||
| end | ||||
| @@ -45,7 +45,7 @@ function Transition.expandUp(args) | ||||
| 	return function() | ||||
| 		local finished = tween:update(1) | ||||
| 		args.canvas:move(args.x, math.floor(pos.y)) | ||||
| 		args.canvas:dirty() | ||||
| 		args.canvas.parent:dirty(true) | ||||
| 		return not finished | ||||
| 	end | ||||
| end | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 kepler155c@gmail.com
					kepler155c@gmail.com