calculator.lua 12.5 KB
-- calculator.lua

-- ----------------------------------------------------------------
local function get_gui_root(player)
	return player.gui.screen
end

-- ----------------------------------------------------------------
function show_rant(player, enabled) 
	local root = get_gui_root(player)
	
	if enabled then
		root.calcui.calcui_table.calcui_table_col2.calcui_rant.sprite = "sprite_calcui_rant"
		root.calcui.calcui_table.calcui_table_col2.calcui_scroll_pane.style.height = 196
	else
		root.calcui.calcui_table.calcui_table_col2.calcui_rant.sprite = nil
		root.calcui.calcui_table.calcui_table_col2.calcui_scroll_pane.style.height = 252
	end
end

-- ----------------------------------------------------------------
local function destroy_calculator(player)
	local root = get_gui_root(player)
	if root.calcui then
		root.calcui.destroy()
	end
end

-- ----------------------------------------------------------------
function show_calculator(player)
	local root = get_gui_root(player)
	destroy_calculator(player)
	
	local calcui = root.add({
		type = "frame", 
		name = "calcui", 
		style = "dialog_frame",
		direction = "vertical"
	})
	
	local flow = calcui.add({
		type = "flow", 
		name = "calcui_flow"
	})
	flow.style.horizontally_stretchable = "on"
	
	flow.add({
		type = "label", 
		caption = {"calculator-ui.title"}, 
		style = "frame_title"
	}).drag_target = calcui
	
	local widget = flow.add({
		type = "empty-widget",
		style = "draggable_space_header",
		name = "calcui_drag"
	})
	widget.drag_target = calcui
	widget.style.horizontally_stretchable = "on"
	widget.style.minimal_width = 24
	widget.style.natural_height = 24
	
	flow.add({
		type = "sprite-button", 
		sprite = "utility/close_white", 
		style = "frame_action_button", 
		name = "calcui_close"
	})
	
	local table = calcui.add({
		type = "table", 
		name = "calcui_table", 
		column_count = "2", 
		vertical_centering = "false"
	})
	
	local col1 = table.add({
		type = "flow", 
		name = "calcui_table_col1", 
		direction = "vertical"
	})
	
	local display = col1.add({
		type = "textfield", 
		style = "calcui_textfield_style", 
		caption = "", 
		name = "calcui_display"
	})

	local row1 = col1.add({type="flow", name="calcui_col1_row1", direction="horizontal"})
	row1.add({type="button", style="calcui_button_style", caption="CE", name="calcui_button_CE"})		-- CE = Clear Entry (just this line)
	row1.add({type="button", style="calcui_button_style", caption="C",  name="calcui_button_C"})		-- C  = Clear (all, past results as well)
	row1.add({type="button", style="calcui_button_style", caption="BS", name="calcui_button_BS"})
	row1.add({type="button", style="calcui_button_style", caption="/",  name="calcui_button_DIV"})

	local row2 = col1.add({type="flow", name="calcui_col1_row2", direction="horizontal"})
	row2.add({type="button", style="calcui_button_style", caption="7",  name="calcui_button_7"})
	row2.add({type="button", style="calcui_button_style", caption="8",  name="calcui_button_8"})
	row2.add({type="button", style="calcui_button_style", caption="9",  name="calcui_button_9"})
	row2.add({type="button", style="calcui_button_style", caption="*",  name="calcui_button_MUL"})

	local row3 = col1.add({type="flow", name="calcui_col1_row3", direction="horizontal"})
	row3.add({type="button", style="calcui_button_style", caption="4",  name="calcui_button_4"})
	row3.add({type="button", style="calcui_button_style", caption="5",  name="calcui_button_5"})
	row3.add({type="button", style="calcui_button_style", caption="6",  name="calcui_button_6"})
	row3.add({type="button", style="calcui_button_style", caption="-",  name="calcui_button_SUB"})

	local row4 = col1.add({type="flow", name="calcui_col1_row4", direction="horizontal"})
	row4.add({type="button", style="calcui_button_style", caption="1",  name="calcui_button_1"})
	row4.add({type="button", style="calcui_button_style", caption="2",  name="calcui_button_2"})
	row4.add({type="button", style="calcui_button_style", caption="3",  name="calcui_button_3"})
	row4.add({type="button", style="calcui_button_style", caption="+",  name="calcui_button_ADD"})

	local row5 = col1.add({type="flow", name="calcui_col1_row5", direction="horizontal"})
	row5.add({type="button", style="calcui_button_style", caption="%",  name="calcui_button_PERC"})
	row5.add({type="button", style="calcui_button_style", caption="0",  name="calcui_button_0"})
	row5.add({type="button", style="calcui_button_style", caption=".",  name="calcui_button_DOT"})
	row5.add({type="button", style="calcui_button_style", caption="=",  name="calcui_button_EQU"})		


	local col2 = table.add({
		type = "flow", 
		name = "calcui_table_col2", 
		direction = "vertical"
	})

	local result = col2.add({
		type = "label", 
		caption = "= ", 
		name = "calcui_display_result"
	})
	result.style.font = "default-large"
	
	local rant = col2.add({
		type = "sprite",
		name = "calcui_rant"
	})
	
	col2.add({
		type = "line", 
		direction = "horizontal"
	})
	
	local scroll = col2.add({
		type = "scroll-pane",
		name = "calcui_scroll_pane"
	})
	scroll.style.height = 252
	
	local recents = scroll.add({
		type = "table", 
		caption = "", 
		name = "calcui_result_table", 
		column_count = "2"
	})
	recents.style.column_alignments[1] = "right"
	
	
	-- center the gui
	calcui.force_auto_center()
end

-- ----------------------------------------------------------------
function hide_calculator(player)
	destroy_calculator(player)
end

-- ----------------------------------------------------------------
function toggle_calculator(player)
	local root = get_gui_root(player)
	if root and root.calcui then
		hide_calculator(player)
	else
		show_calculator(player)
	end
end

-- ----------------------------------------------------------------
function clear_equation(player)
	local root = get_gui_root(player)
	root.calcui.calcui_table.calcui_table_col1.calcui_display.text = ""
end

-- ----------------------------------------------------------------
function process_ce_key(player, button)
	local root = get_gui_root(player)
	clear_equation(player)
	root.calcui.calcui_table.calcui_table_col2.calcui_display_result.caption = "="
end

-- ----------------------------------------------------------------
function process_c_key(player, button)
	local root = get_gui_root(player)
	process_ce_key(player, button)
	root.calcui.calcui_table.calcui_table_col2.calcui_scroll_pane.calcui_result_table.clear()
end

-- ----------------------------------------------------------------
function process_backspace_key(player, button)
	local root = get_gui_root(player)
	root.calcui.calcui_table.calcui_table_col1.calcui_display.text = string.sub(root.calcui.calcui_table.calcui_table_col1.calcui_display.text, 1, -2)
end

-- ----------------------------------------------------------------
function fix_equation(equation)
	local result = equation
	
	-- fix math library shortcuts
	local math_lib = {
		["abs"]   = "math.abs",
		["acos"]  = "math.acos",
		["asin"]  = "math.asin",
		["atan"]  = "math.atan",
		["ceil"]  = "math.ceil",
		["floor"] = "math.floor",
		["cos"]   = "math.cos",
		["sin"]   = "math.sin",
		["tan"]   = "math.tan",
		["deg"]   = "math.deg",
		["rad"]   = "math.rad",
		["exp"]   = "math.exp",
		["log"]   = "math.log",
		["min"]   = "math.min",
		["max"]   = "math.max",
		["modf"]  = "math.modf",
		["sqrt"]  = "math.sqrt",
		["huge"]  = "math.huge",
		["pi"]    = "math.pi"
	}
	for key, val in pairs(math_lib) do
		result = result:gsub(key, val)
	end

	-- fix percentage
	result = result:gsub("(%%)", "/100")
	
	return result
end

-- ----------------------------------------------------------------
function process_equal_key(player, button)
	local root = get_gui_root(player)
	
	local original_equation = root.calcui.calcui_table.calcui_table_col1.calcui_display.text;
	
	equation = fix_equation(original_equation)
	
	-- just testing
	--root.calcui.calcui_table.calcui_table_col1.calcui_display.text = equation
	
	if not (equation == nil or equation == "") then
		local status, retval = pcall(function()
			return load("return " .. equation)()
		end)
		root.calcui.calcui_table.calcui_table_col2.calcui_display_result.tooltip = retval
		status, retval = pcall(function()
			return tonumber(string.format("%." .. settings.get_player_settings(player)["calcui-decimal-places"].value .. "f", retval))
		end)
		if retval == nil or retval == "" then
			status = false
		end
		if not status then
			retval = "NaN"
			show_rant(player, true)
		else
			if retval <= 0 then
				show_rant(player, true)
			else 
				show_rant(player, false)
			end
		end
		root.calcui.calcui_table.calcui_table_col2.calcui_display_result.caption = "= " .. retval
		
		-- only write in recent table if actually a result
		if status then
			-- check last equation and only insert if not the same
			local item_size = #root.calcui.calcui_table.calcui_table_col2.calcui_scroll_pane.calcui_result_table.children
			
			if item_size == 0 or root.calcui.calcui_table.calcui_table_col2.calcui_scroll_pane.calcui_result_table.children[item_size-1].caption ~= original_equation then
				local recent_equation = root.calcui.calcui_table.calcui_table_col2.calcui_scroll_pane.calcui_result_table.add({
					type = "label", 
					name = "calcui_recent_equation_" .. item_size, 
					caption = original_equation,
					tooltip = {"calculator-ui.recent_tooltip"}
				})
				local recent_result = root.calcui.calcui_table.calcui_table_col2.calcui_scroll_pane.calcui_result_table.add({
					type = "label", 
					name = "calcui_recent_result_" .. item_size, 
					caption = root.calcui.calcui_table.calcui_table_col2.calcui_display_result.caption
				})
				root.calcui.calcui_table.calcui_table_col2.calcui_scroll_pane.scroll_to_bottom()
			end
		end
	end

	if settings.get_player_settings(player)["calcui-clear-on-calc"].value then
		clear_equation(player)
	end
end

-- ----------------------------------------------------------------
function display_addchar(player, char)
	local root = get_gui_root(player)
	root.calcui.calcui_table.calcui_table_col1.calcui_display.text = root.calcui.calcui_table.calcui_table_col1.calcui_display.text .. char
	show_rant(player, false)
end

-- ----------------------------------------------------------------
local button_dispatch = {
	["CE"]  = process_ce_key,
	["C"]   = process_c_key,
	["BS"]  = process_backspace_key,
	--
	["EQU"] = process_equal_key
}
local button_addchar = {
	["DIV"]  = "/",
	--
	["7"]    = "7",
	["8"]    = "8",
	["9"]    = "9",
	["MUL"]  = "*",
	--
	["4"]    = "4",
	["5"]    = "5",
	["6"]    = "6",
	["SUB"]  = "-",
	--
	["1"]    = "1",
	["2"]    = "2",
	["3"]    = "3",
	["ADD"]  = "+",
	--
	["PERC"] = "%",
	["0"]    = "0",
	["DOT"]  = "."
}

function handle_calcui_click(event, player)
	debug_print("handle_calcui_click()")
	local event_name = event.element.name
	local button_prefix = "calcui_button_"
	local button_prefix_len = string.len(button_prefix)
	
	local recent_prefix = "calcui_recent_"
	local recent_prefix_len = string.len(recent_prefix);
	
	-- calculator buttons
	if string.sub(event_name, 1, button_prefix_len) == button_prefix then
		show_rant(player, false)

		button = string.sub(event_name, button_prefix_len + 1 )
		debug_print("handle_calcui_click button " .. button)
		local dispatch_func = button_dispatch[button]
		if dispatch_func then
			dispatch_func(player, button)
		end
		
		local addchar = button_addchar[button]
		if addchar then
			display_addchar(player, addchar)
		end
	-- close button
	elseif event_name == "calcui_close" then
		hide_calculator(player)
	-- recent results
	elseif string.sub(event_name, 1, recent_prefix_len) == recent_prefix then
		if event.button == defines.mouse_button_type.left and 
		   event.shift == true then
			-- copy equation to display
			local root = get_gui_root(player)
			root.calcui.calcui_table.calcui_table_col1.calcui_display.text = root.calcui.calcui_table.calcui_table_col2.calcui_scroll_pane.calcui_result_table[event_name].caption
			root.calcui.calcui_table.calcui_table_col1.calcui_display.focus()
		end
	end
end

-- ----------------------------------------------------------------
function calcui_on_gui_text_changed(event)
	if event.element.name == "calcui_display" then
		local player = game.players[event.player_index]
		local root = get_gui_root(player)
		if string.find(root.calcui.calcui_table.calcui_table_col1.calcui_display.text, "=") then
			root.calcui.calcui_table.calcui_table_col1.calcui_display.text = root.calcui.calcui_table.calcui_table_col1.calcui_display.text:gsub("=", "")
			process_equal_key(player)
		end
	end
end

-- ----------------------------------------------------------------
function calcui_on_gui_location_changed(event)
	if event.element.name == "calcui" then
		local player = game.players[event.player_index]
		local root = get_gui_root(player)
		root.calcui.location = event.element.location
	end
end