View difference between Paste ID: RUxxwBmn and eMnFx0m1
SHOW: | | - or go back to the newest paste.
1-
-- Version: Unspeakable Crawling Skull
1+
-- Version: Unspeakable Crawling Skully
2
3
Texter = {}
4
Texter._LastInput = {
5
	Str  = "",
6
	Args = "1, 3",
7
	Font = "7px",
8
	Mode = 0
9
}
10
Texter._Fonts = {}
11
12
------------------ User config zone -------------------
13
Texter.FONT_FOLDERS_TO_LOAD = {"TexterFonts", "Scripts", "%."}   -- All file will be "dofile" under those foler name, the root dir must be written regex form %.
14-
Texter.DISPLAY_MESSAGES     = true              -- Should show the startup message [true: show, false: don't show]
14+
Texter.DISPLAY_MESSAGES     = false              -- Should show the startup message [true: show, false: don't show]
15
Texter.FONT_SEARCH_DEPTH    = 3                 -- How deep should texter search the font folder [0: only the root folder]
16
---------------- User config zone ends ----------------
17
18
function Texter.Init(register)
19
	Texter._Fontmatrix = {}
20
	Texter._Fonts = {}
21
	-- Please don't let Texter too far away from font file :)
22
	local fontFiles = Texter.LoadAllFontsInFolder(".", Texter.FONT_FOLDERS_TO_LOAD, Texter.FONT_SEARCH_DEPTH)
23
	local msg = ""
24
	if(Texter._Fontmatrix ~= nil) then
25
		for fontName in pairs(Texter._Fontmatrix) do
26
			table.insert(Texter._Fonts, fontName)
27
		end
28
		msg = "Texter: "..#fontFiles.." font file(s) loaded, "..#Texter._Fonts.." font(s) available in total."
29
	else
30
		msg = "Texter: No font found."
31
	end
32
	if( Texter.DISPLAY_MESSAGES ) then
33
		tpt.log(msg)
34
	end
35
	if( register == nil or register == true ) then
36
		tpt.register_keypress(Texter._HotkeyHandler)
37
	end
38
end
39
40
-- Internal methods
41
function Texter._IsFolder(path)
42
	local _isFolder = false
43
	if(path ~= nil) then
44-
		-- API bug, not fixed right now (ver 85.0)
44+
		-- API bug, fixed(ver 86.0)
45
		-- if( fs.isDirectory(path)  ) then
46
				-- _isFolder = true
47
		-- end
48
		if( fs.isDirectory(path) == fs.isDirectory(".") ) then
49
			_isFolder = true
50
		end
51
	end
52
	return _isFolder
53
end
54
function Texter._GetFontHeight(fontName)
55
	local height = 0
56
	if( Texter._Fontmatrix ~= nil and Texter._Fontmatrix[fontName] ~=nil and Texter._Fontmatrix[fontName].height ~= nil  ) then
57
		height = Texter._Fontmatrix[fontName].height
58
	else
59
		height = 7 -- 7 is a lucky number
60
	end
61
	return height
62
end
63
function Texter._Input(eventHandler, fonts, paramsText, strText, fontText, mode)
64
	local controlHeight = 17
65
	if( paramsText == nil  ) then paramsText = "" end
66
	if( strText    == nil  ) then strText    = "" end
67
	if( fontText   == nil  ) then fontText   = "" end
68
	if( mode       == nil  ) then mode       = 0  end
69
	if( fonts      == nil  ) then fonts      = {} end
70
	
71
	local isContains = function(inTable, value)
72
		local isContain = false
73
		for i=1, #inTable do
74
			if( value == inTable[i]  ) then
75
				isContain = true
76
				break
77
			end
78
		end
79
		return isContain
80
	end
81
	
82
	local TexterWin     = Window:new(-1, -1, 500, 100)
83
	local paramsBox     = Textbox:new(10, controlHeight + 15, 150, controlHeight, paramsText, "ptype, hspc, vspc")
84
	local paramsLabel   = Label:new(11, 9, 130, controlHeight, "Element type and\nhorizontal/vertical spacing.")
85
	local modeBox       = Button:new(170, controlHeight + 15, 150, controlHeight, "No mode available")
86
	local modeLabel     = Label:new(171, 9, 150, controlHeight, "Font mode you want to use.")
87
	local stringBox     = Textbox:new(10, 57, 480, controlHeight, strText, "Input text to create under mouse. Use \\n for new line, \\\\n for \\n. Sorry, you can't get \\\\n.")
88
	local fontBtn       = Button:new(330, controlHeight + 15, 150, controlHeight, "No font available")
89
	local fontLabel     = Label:new(331, 9, 150, controlHeight, "Select the font you want to use.")
90
	local cancelBtn     = Button:new(0, 83, 251, controlHeight, "Cancel")
91
	cancelBtn:action(function(sender)interface.closeWindow(TexterWin)end)
92
	local okayBtn = Button:new(250, 83, 250, controlHeight, "Okay")
93
	okayBtn:action(
94
		function(sender)
95
			if( eventHandler ~= nil and type(eventHandler) == "function"  ) then
96
				-- Use event because I can't make things like tpt.input
97
				eventHandler(paramsBox:text(), stringBox:text(), fontBtn:text(), tonumber(modeBox:text()))
98
			end
99
			interface.closeWindow(TexterWin)
100
		end
101
	)
102
	if( mode >= 0 ) then
103
		modeBox:text(tostring(mode))
104
	end
105
	modeBox:action(
106
		function(sender)
107
			local input = tpt.input("Font mode", "The draw mode:\n0  Ignore all additional information\n+1 Keep the ptype in font\n+2 Keep the dcolor in font\nAdd your choices up then type in.")
108
			local mode = tonumber(input)
109
			if( mode~= nil and mode >= 0 )then
110
				sender:text(input)
111
			else
112
				sender:text("0")
113
			end
114
		end
115
	)
116
	if(#fonts > 0) then
117
		if( isContains(fonts, fontText)  ) then
118
			fontBtn:text(fontText)
119
		else
120
			fontBtn:text(fonts[1])
121
		end
122
		fontBtn:action(
123
			function(sender)
124
				local winX, winY = TexterWin:position()
125
				local btnX, btnY = fontBtn:position()
126
				local btnW, btnH = fontBtn:size()
127
				local listWin = Window:new(winX+btnX, winY+btnY-controlHeight*(#fonts-1)/2, btnW, (btnH-1)*#fonts)
128
				for i=1, #fonts do
129
					local optionBtn = Button:new(0, (btnH-1)*(i-1), btnW, btnH, fonts[i])
130
					optionBtn:action(
131
						function(sender)
132
							interface.closeWindow(listWin)
133
							fontBtn:text(optionBtn:text())
134
						end
135
					)
136
					listWin:addComponent(optionBtn)
137
				end
138
				interface.showWindow(listWin)
139
			end
140
		)
141
	end
142
	
143
	TexterWin:addComponent(cancelBtn)
144
	TexterWin:addComponent(okayBtn)
145
	TexterWin:addComponent(stringBox)
146
	TexterWin:addComponent(paramsBox)
147
	TexterWin:addComponent(paramsLabel)
148
	TexterWin:addComponent(modeBox)
149
	TexterWin:addComponent(modeLabel)
150
	TexterWin:addComponent(fontBtn)
151
	TexterWin:addComponent(fontLabel)
152
	
153
	interface.showWindow(TexterWin)
154
end
155
function Texter._FindAllFile(rootPath, foldersToLoad, depth)
156
	if(  rootPath == nil  ) then
157
		rootPath = "."
158
	end
159
	if(  depth == nil  ) then
160
		depth = 1
161
	end
162
	local files = {}
163
	local isTargetFolder = false
164
	for i, folderName in ipairs(foldersToLoad) do 
165
		if( string.match(rootPath, "\\?"..folderName.."$") ~= nil ) then
166
			isTargetFolder = true
167
			break
168
		end
169
	end
170
	if( isTargetFolder ) then  -- If not the folder we want, ignore it
171
		files = fs.list(rootPath)
172
	end
173
	
174
	-- Trim match array
175
	local index = 1 -- Lua fool
176
	for i=1, #files do
177
		if( files[index] == "Texter.lua" 
178
		  or string.match(files[index], "%.texterfont$") == nil
179
		  or Texter._IsFolder(rootPath.."\\"..files[index]) ) then
180
			table.remove(files, index)
181
		else
182
			files[index] = rootPath.."\\"..files[index] -- full path
183
			index = index + 1
184
		end
185
	end
186
	
187
	-- Check subs
188
	if(  depth > 0  ) then
189
		local subs = fs.list(rootPath)
190
		local subFiles = nil
191
		for i=1, #subs do
192
			if( Texter._IsFolder(rootPath.."\\"..subs[i]) ) then
193
				subFiles = Texter._FindAllFile(rootPath.."\\"..subs[i], foldersToLoad, depth - 1)
194
				Texter._AppendArray(files, subFiles)
195
			end
196
		end
197
	end
198
	return files
199
end
200
function Texter._AppendArray(oriArray, arrayToAppend) -- Append an array to the original array
201
	if(  oriArray~= nil and arrayToAppend ~= nil and #arrayToAppend>0  ) then
202
		for i=1, #arrayToAppend do
203
			table.insert(oriArray, arrayToAppend[i])
204
		end
205
	end
206
	return oriArray
207
end
208
209
-- Helper methods and handlers
210
function Texter._HotkeyHandler(key, keyNum, modifier, event) -- Hotkey handler
211
	if( event==1 and keyNum==116 and modifier==64  ) then -- Ctrl + t
212
		-- Additional settings
213
		local ptype = elements[tpt.selectedl]
214
		if( ptype == nil  ) then
215
			ptype = 1 --"DUST"
216
		end
217
		-- Prompt
218
		Texter._Input(
219
			Texter._InputHandler,
220
			Texter._Fonts,
221
			elements.property(ptype, "Name")..", "..Texter._LastInput.Args,
222
			Texter._LastInput.Str,
223
			Texter._LastInput.Font,
224
			Texter._LastInput.Mode
225
		)
226
	end
227
end
228
function Texter._InputHandler(params, str, fontName, mode) -- Input handler
229
	local args = {}
230
	if( string.len(str) > 0  ) then
231
		Texter._LastInput.Str = str
232
		str = string.gsub(str, "([^\\]?)\\n", "%1\n")  -- Small trick
233
		str = string.gsub(str, "([^\\]?)\\\\n", "%1\\n")
234
	end
235
	
236
	if( string.len(params) > 0  ) then Texter._LastInput.Args = string.gsub(params, "%s*%w*%s*,%s*(.*)", "%1") end
237
	local i=1
238
	for arg in string.gmatch(params, "%s*(%w*)%s*,?") do
239
		args[i] = arg
240
		i = i+1
241
	end
242
	Texter._LastInput.Font = fontName
243
	Texter._LastInput.Mode = mode
244
	
245
	if( pcall(tpt.element, args[1]) == false  ) then
246
		args[1] = "DUST"
247
	end
248
	Texter.Tstr(str, tpt.mousex, tpt.mousey, args[1], mode, args[2], args[3], fontName)
249
end
250
251
-- APIs
252
function Texter.LoadAllFontsInFolder(rootPath, foldersToLoad, depth) -- Load all fonts in target folder(s)
253
	local fonts = Texter._FindAllFile(rootPath, foldersToLoad, depth)
254
	if(  fonts ~= nil  ) then
255
		for i=1, #fonts do
256
			dofile(fonts[i])
257
		end
258
	end
259
	return fonts
260
end
261
function Texter.Tchar(char, x, y, ptype, mode, fontName) -- Draw a single character
262
	local mtx     = {}
263
	local letter  = {}
264
	local PTYPE_MASK  = 0xFF
265
	local DCOLOR_MASH = 0xFFFFFFFF
266
	local DCOLOR_OFFSET = 8
267
	local margin_L = 0 -- margin left
268
	local margin_R = 0 -- margin right
269
	local margin_T = 0 -- margin top
270
	-- if given font not available, use the first available one
271
	if( fontName == nil or Texter._Fontmatrix[fontName] == nil  ) then
272
		for font in pairs(Texter._Fontmatrix) do
273
			fontName = font
274
			break
275
		end
276
	end
277
	-- if still not available, break
278
	if( fontName == nil  ) then return 0 end
279
	
280
	-- get character data
281
	letter = Texter._Fontmatrix[fontName][char]
282
	if( letter == nil  ) then
283
		letter = Texter._Fontmatrix[fontName]["nil"]
284
	end
285
	if( letter == nil  ) then return 0 end -- ["nil"] not defined
286
	mtx = letter.Mtx
287
	if( mtx == nil  ) then mtx = {} end
288
	if( letter.Margin ~= nil  ) then
289
		if(letter.Margin.Left  ~= nil)then margin_L = letter.Margin.Left  end
290
		if(letter.Margin.Right ~= nil)then margin_R = letter.Margin.Right end
291
		if(letter.Margin.Top   ~= nil)then margin_T = letter.Margin.Top   end
292
	end
293
	
294
	local width  = 0
295
	for i=1, #mtx do --mtx height
296
		if(#mtx[i] > width)then width = #mtx[i] end
297
		for j=1, width do
298
			local particle = mtx[i][j]
299
			if( particle~=0  ) then
300
				local success = false
301
				local p = {}
302
				p.ptype  = bit.band(particle                , PTYPE_MASK )
303
				p.dcolor = bit.band(particle/2^DCOLOR_OFFSET, DCOLOR_MASH) -- bit.rshift can only handle 5 bits :(
304
				if( ptype == 0 or ptype == "0" ) then
305
					pcall(tpt.delete, x+j-1+margin_L, y+i-1+margin_T) 
306
				else
307
					-- mode 0 use the given type
308
					--     +1 use the font ptype only
309
					--     +2 use the font dcolor only
310
					if( bit.band(mode, 1) ~= 1 ) then
311
						p.ptype = ptype
312
					end
313
					-- tpt.log("particle is "..particle..", ptype is "..ptype..", to draw is "..p.ptype)-- debug
314
					pcall(tpt.create, x+j-1+margin_L, y+i-1+margin_T, p.ptype)
315
					if( bit.band(mode, 2) == 2 ) then -- Paint it even when failed to create. Because there might be existed particle
316
						-- tpt.log("Try to draw dcolor: "..p.dcolor.." ( 0x"..string.format("%X", p.dcolor).." )") --debug
317-
						pcall(tpt.set_property, p.dcolor, x+j-1+margin_L, y+i-1+margin_T)
317+
						pcall(tpt.set_property, "dcolor", p.dcolor, x+j-1+margin_L, y+i-1+margin_T, 1, 1)
318
					end
319
				end
320
			end
321
		end
322
	end
323
	width = width + margin_L + margin_R
324
	return width
325
end
326
function Texter.Tstr(str, x, y, ptype, mode, hspacing, vspacing, fontName) -- Draw a string
327
	local ox    = 0
328
	local oy    = 0
329
	local oy    = 0
330
	local fontH = Texter._GetFontHeight(fontName)
331
	if( mode == nil  ) then
332
		mode = 0
333
	end
334
	if( hspacing == nil  ) then
335
		hspacing = 1
336
	end
337
	if( vspacing == nil  ) then
338
		vspacing = 3
339
	end
340
	for i=1,string.len(str) do
341
		if( string.sub(str, i, i) == "\n"  ) then
342
			oy = vspacing + oy + fontH
343
			ox = 0
344
		else
345
			ox = hspacing + ox + Texter.Tchar(string.sub(str, i, i), x+ox, y+oy, ptype, mode, fontName)
346
		end
347
	end
348
	return string.len(str)
349
end
350
function T(str, ptype, mode, hspc, vspc, fontName) -- Shortcut for better user experience
351
	if( ptype == nil  ) then
352
		ptype = elements[tpt.selectedl] -- elements.property(tpt.selectedl, "Name")
353
	end
354
	if( ptype == nil  ) then
355
		ptype = "DUST"
356
	end
357
	if( str == nil  ) then str = "" end
358
	Texter.Tstr(str, tpt.mousex, tpt.mousey, ptype, mode, hspc, vspc, fontName)
359
	return string.len(str)
360
end
361
362
Texter.Init()