From 9724fb73abf9fd3760a4f3f3ac941a119708c97b Mon Sep 17 00:00:00 2001 From: dam Date: Thu, 16 Dec 2021 01:17:34 +0000 Subject: First prototype with working database and staging area. Fix incorrect warp in date picker. Disable physics engine. --- logic/stage.gd | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 logic/stage.gd (limited to 'logic/stage.gd') diff --git a/logic/stage.gd b/logic/stage.gd new file mode 100644 index 0000000..a9f4fee --- /dev/null +++ b/logic/stage.gd @@ -0,0 +1,88 @@ +extends Control +class_name Stage + +signal save # (database_entry: Dictionary) +signal discard # () + +onready var process_id: LineEdit = get_node("controls/process_id") +onready var surgery_id: LineEdit = get_node("controls/surgery_id") +onready var date: DatePicker = get_node("controls/date_picker") +onready var place: LineEdit = get_node("controls/place") +onready var anesthetic: LineEdit = get_node("controls/anesthetic") +onready var first_assistant: LineEdit = get_node("controls/first_assistant") +onready var type: LineEdit = get_node("controls/type") +onready var sub_type: LineEdit = get_node("controls/sub_type") +onready var sub_sub_type: LineEdit = get_node("controls/sub_sub_type") +onready var pathology: LineEdit = get_node("controls/pathology") +onready var intervention: LineEdit = get_node("controls/intervention") +onready var is_urgency: Button = get_node("controls/is_urgency") +onready var notes: LineEdit = get_node("controls/notes") +onready var save_button: Button = get_node("controls/save") +onready var discard_button: Button = get_node("controls/discard") + + +func _ready(): + save_button.connect("pressed", self, "save_action") + discard_button.connect("pressed", self, "discard_action") + + +func save_action(): + self.visible = false + emit_signal("save", get_stage()) + + +func discard_action(): + self.visible = false + emit_signal("discard") + + +func set_stage(entry: Dictionary): + process_id.text = entry.process_id + surgery_id.text = entry.surgery_id + date.set_date(entry.date_year, entry.date_month, entry.date_day) + place.text = entry.place + anesthetic.text = entry.anesthetic + first_assistant.text = entry.first_assistant + type.text = entry.type + sub_type.text = entry.sub_type + sub_sub_type.text = entry.sub_sub_type + pathology.text = entry.pathology + intervention.text = entry.intervention + is_urgency.pressed = entry.is_urgency + notes.text = entry.notes + self.scroll_vertical = 0 # @DAM TODO + + +func get_stage() -> Dictionary: + var entry: Dictionary + entry.process_id = process_id.text + entry.surgery_id = surgery_id.text + entry.date_year = date.get_year() + entry.date_month = date.get_month() + entry.date_day = date.get_day() + entry.place = place.text + entry.anesthetic = anesthetic.text + entry.first_assistant = first_assistant.text + entry.type = type.text + entry.sub_type = sub_type.text + entry.sub_sub_type = sub_sub_type.text + entry.pathology = pathology.text + entry.intervention = intervention.text + entry.is_urgency = is_urgency.pressed + entry.notes = notes.text + return entry + + +func _notification(what: int): + if what == MainLoop.NOTIFICATION_WM_GO_BACK_REQUEST: + discard_action() + + +func _unhandled_input(event): + get_node("/root/main/debug").text += "%s\n" % event.to_string() + + +func _unhandled_key_input(event): + get_node("/root/main/debug").text += "%s\n" % event.to_string() + + -- cgit v1.2.3 From 3f29bb41d713240eb7ba831f68981674d4db9eb9 Mon Sep 17 00:00:00 2001 From: dam Date: Thu, 16 Dec 2021 17:04:09 +0000 Subject: Fix issues when creating/editing entries. Fix android icon. --- export_presets.cfg | 6 ++--- icons/icon.png | Bin 19327 -> 42947 bytes icons/icon.xcf | Bin 0 -> 112237 bytes icons/icon_background.png | Bin 0 -> 36824 bytes icons/icon_background.png.import | 35 ++++++++++++++++++++++++++ icons/icon_foreground.png | Bin 0 -> 6327 bytes icons/icon_foreground.png.import | 35 ++++++++++++++++++++++++++ logic/database.gd | 52 ++++++++++++++++++--------------------- logic/stage.gd | 45 +++++++++++++++++++++------------ project.godot | 2 +- 10 files changed, 127 insertions(+), 48 deletions(-) create mode 100644 icons/icon.xcf create mode 100644 icons/icon_background.png create mode 100644 icons/icon_background.png.import create mode 100644 icons/icon_foreground.png create mode 100644 icons/icon_foreground.png.import (limited to 'logic/stage.gd') diff --git a/export_presets.cfg b/export_presets.cfg index ae6c4cf..06377bd 100644 --- a/export_presets.cfg +++ b/export_presets.cfg @@ -35,9 +35,9 @@ package/name="" package/signed=true package/classify_as_game=false package/retain_data_on_uninstall=false -launcher_icons/main_192x192="" -launcher_icons/adaptive_foreground_432x432="" -launcher_icons/adaptive_background_432x432="" +launcher_icons/main_192x192="res://icons/icon.png" +launcher_icons/adaptive_foreground_432x432="res://icons/icon_foreground.png" +launcher_icons/adaptive_background_432x432="res://icons/icon_background.png" graphics/32_bits_framebuffer=true graphics/opengl_debug=false xr_features/xr_mode=0 diff --git a/icons/icon.png b/icons/icon.png index 4b35a32..63760ec 100755 Binary files a/icons/icon.png and b/icons/icon.png differ diff --git a/icons/icon.xcf b/icons/icon.xcf new file mode 100644 index 0000000..abfbec1 Binary files /dev/null and b/icons/icon.xcf differ diff --git a/icons/icon_background.png b/icons/icon_background.png new file mode 100644 index 0000000..6ee62e1 Binary files /dev/null and b/icons/icon_background.png differ diff --git a/icons/icon_background.png.import b/icons/icon_background.png.import new file mode 100644 index 0000000..7b6caab --- /dev/null +++ b/icons/icon_background.png.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/icon_background.png-7c479113bc74649f6884d5867344a330.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icons/icon_background.png" +dest_files=[ "res://.import/icon_background.png-7c479113bc74649f6884d5867344a330.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +process/normal_map_invert_y=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/icons/icon_foreground.png b/icons/icon_foreground.png new file mode 100644 index 0000000..919f2ff Binary files /dev/null and b/icons/icon_foreground.png differ diff --git a/icons/icon_foreground.png.import b/icons/icon_foreground.png.import new file mode 100644 index 0000000..8427899 --- /dev/null +++ b/icons/icon_foreground.png.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/icon_foreground.png-386f0ed4375c2b690a85127a54f3c7b2.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icons/icon_foreground.png" +dest_files=[ "res://.import/icon_foreground.png-386f0ed4375c2b690a85127a54f3c7b2.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +process/normal_map_invert_y=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/logic/database.gd b/logic/database.gd index 1abb984..db449ae 100644 --- a/logic/database.gd +++ b/logic/database.gd @@ -52,38 +52,34 @@ func _ready(): add_button.connect("pressed", self, "add_action") stage.connect("save", self, "save") stage.connect("discard", self, "discard") - refresh_list() + for it in db: + self.add_item(get_entry_view(it)) func get_entry_view(database_entry: Dictionary) -> String: return "%6s | %6s | %s" % [database_entry.process_id, database_entry.surgery_id, get_entry_date(database_entry)] -func refresh_list(): - self.clear() - for it in db: - self.add_item(get_entry_view(it)) - - func clear_selection(): unselect_all() func delete_action(): - # @DAM Could do some performance improvements var selected_entries_idx := self.get_selected_items() - var sorted_idx := Array(selected_entries_idx) - sorted_idx.sort() - sorted_idx.invert() - for idx in sorted_idx: - db.remove(idx) - self.remove_item(idx) + if selected_entries_idx.size() == 0: + return + assert(selected_entries_idx.size() == 1, "Multiple selected items not supported.") + var selected_idx = selected_entries_idx[0] + db.remove(selected_idx) + self.remove_item(selected_idx) + store_database() func edit_action(): var selected_entries_idx := self.get_selected_items() - if selected_entries_idx.size() != 1: + if selected_entries_idx.size() == 0: return + assert(selected_entries_idx.size() == 1, "Multiple selected items not supported.") staged_idx = selected_entries_idx[0] self.visible = false stage.visible = true @@ -95,20 +91,17 @@ func add_action(): stage.visible = true var staged := instance_entry() stage.set_stage(staged) - # @DAM This signal is connected but, if we select "DISCARD" it stays connectd. - # Maybe I should always keep the connection and use two different buttons on sage screen. - stage.connect("save", self, "save_new", [], CONNECT_ONESHOT) func save(database_entry: Dictionary): + database_entry = instance_entry(database_entry) # @DAM Maybe we could not be creating endless dictionaries? if staged_idx >= 0: db[staged_idx] = database_entry set_item_text(staged_idx, get_entry_view(database_entry)) -# items[staged_idx] = get_entry_view(database_entry) else: db.append(database_entry) add_item(get_entry_view(database_entry)) - + store_database() staged_idx = -1 self.visible = true @@ -118,9 +111,9 @@ func discard(): self.visible = true -func load_database(): +func load_database(file_path: String = DATABASE_FILE_PATH): var file := File.new() - file.open(DATABASE_FILE_PATH, File.READ_WRITE) + file.open(file_path, File.READ_WRITE) var headers: PoolStringArray var is_first_line := true # while database_file.eof_reached() == false: @@ -177,9 +170,9 @@ func load_database(): ] -func store_database(): +func store_database(file_path: String = DATABASE_FILE_PATH): var file := File.new() - file.open(DATABASE_FILE_PATH, File.WRITE) + file.open(file_path, File.WRITE) var header := PoolStringArray(ENTRY_PROTOTYPE.keys()) file.store_csv_line(header) var entry := PoolStringArray() @@ -188,14 +181,17 @@ func store_database(): file.close() -func instance_entry(params: Dictionary = {}) -> Dictionary: +static func instance_entry(params: Dictionary = {}) -> Dictionary: var new_entry := ENTRY_PROTOTYPE.duplicate(true) new_entry.process_id = params.get("process_id", "") new_entry.surgery_id = params.get("surgery_id", "") new_entry.place = params.get("place", "") var today = OS.get_date() - set_entry_date(new_entry, params.get("date", DATE_FORMAT % [today.year, today.month, today.day])) + new_entry.date_year = params.get("date_year", today.year) + new_entry.date_month = params.get("date_month", today.month) + new_entry.date_day = params.get("date_day", today.day) + new_entry.date = params.get("date", get_entry_date(new_entry)) new_entry.anesthetic = params.get("anesthetic", "") new_entry.first_assistant = params.get("first_assistant", "") @@ -210,11 +206,11 @@ func instance_entry(params: Dictionary = {}) -> Dictionary: return new_entry -func get_entry_date(entry: Dictionary) -> String: +static func get_entry_date(entry: Dictionary) -> String: return DATE_FORMAT % [entry.date_year, entry.date_month, entry.date_day] -func set_entry_date(entry: Dictionary, date: String): +static func set_entry_date(entry: Dictionary, date: String): date = date.strip_edges().replace(" ", DATE_SEPARATOR).replace("/", DATE_SEPARATOR).replace("\\", DATE_SEPARATOR) var year_month_idx := date.find(DATE_SEPARATOR) var month_day_idx := date.find(DATE_SEPARATOR, year_month_idx + 1) diff --git a/logic/stage.gd b/logic/stage.gd index a9f4fee..19018fa 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -54,22 +54,35 @@ func set_stage(entry: Dictionary): func get_stage() -> Dictionary: - var entry: Dictionary - entry.process_id = process_id.text - entry.surgery_id = surgery_id.text - entry.date_year = date.get_year() - entry.date_month = date.get_month() - entry.date_day = date.get_day() - entry.place = place.text - entry.anesthetic = anesthetic.text - entry.first_assistant = first_assistant.text - entry.type = type.text - entry.sub_type = sub_type.text - entry.sub_sub_type = sub_sub_type.text - entry.pathology = pathology.text - entry.intervention = intervention.text - entry.is_urgency = is_urgency.pressed - entry.notes = notes.text + +# var entry: Dictionary = Database.instance_entry({ +# "process_id": process_id.text, +# "surgery_id": surgery_id.text, +# "date_year": date.get_year(), +# "date_month": date.get_month(), +# "date_day": date.get_day(), +# "place": place.text, +# "anesthetic": anesthetic.text, +# "first_assistant": first_assistant.text, +# "type": type.text, +# }) + var entry: Dictionary = { + "process_id": process_id.text, + "surgery_id": surgery_id.text, + "date_year": date.get_year(), + "date_month": date.get_month(), + "date_day": date.get_day(), + "place": place.text, + "anesthetic": anesthetic.text, + "first_assistant": first_assistant.text, + "type": type.text, + "sub_type": sub_type.text, + "sub_sub_type": sub_sub_type.text, + "pathology": pathology.text, + "intervention": intervention.text, + "is_urgency": is_urgency.pressed, + "notes": notes.text, + } return entry diff --git a/project.godot b/project.godot index 41969be..584ffa8 100644 --- a/project.godot +++ b/project.godot @@ -43,7 +43,7 @@ run/main_scene="res://main.tscn" boot_splash/image="res://icons/icon.png" boot_splash/fullsize=false boot_splash/use_filter=false -boot_splash/bg_color=Color( 0, 0, 0, 1 ) +boot_splash/bg_color=Color( 0, 0, 0, 0 ) config/icon="res://icons/icon.png" config/quit_on_go_back=false -- cgit v1.2.3 From a2af4c7d07259a091deabdceaa5da1a2f7757c5e Mon Sep 17 00:00:00 2001 From: dam Date: Sat, 18 Dec 2021 03:14:52 +0000 Subject: Add drag detection threshold to date picker. Prototype touch drag on database screen. --- date_picker/value_picker.gd | 7 ++- icons/icon.png | Bin 42947 -> 43811 bytes icons/icon.xcf | Bin 112237 -> 151900 bytes icons/icon_background.png | Bin 36824 -> 37713 bytes logic/database.gd | 137 ++++++++++++++++++++++++++++++++++++++------ logic/stage.gd | 11 +++- main.tscn | 1 + project.godot | 2 +- touch_scroll.gd | 66 +++++++++++++++++++++ 9 files changed, 205 insertions(+), 19 deletions(-) create mode 100644 touch_scroll.gd (limited to 'logic/stage.gd') diff --git a/date_picker/value_picker.gd b/date_picker/value_picker.gd index 5e77294..fcd7dff 100644 --- a/date_picker/value_picker.gd +++ b/date_picker/value_picker.gd @@ -5,6 +5,7 @@ class_name ValuePicker const VELOCITY_DECAYING_FACTOR: float = 5.5 const BINNING_THRESHOLD: float = 0.75 const BINNING_ADJUST_P: float = 5.0 +const DRAG_THRESHOLD_CM: float = 0.250 export var min_value: int export var max_value: int @@ -19,6 +20,7 @@ var anchor: float var value: int var offset: float +var screen_dpcm: float var scroll_unit_height: float var label_previous_base_position: float var label_current_base_position: float @@ -39,6 +41,7 @@ func _ready(): input.connect("focus_entered", self, "input_focus_entered") input.connect("focus_exited", self, "input_focus_exited") + screen_dpcm = float(OS.get_screen_dpi()) / 2.54 scroll_unit_height = label_current.rect_size.y label_previous_base_position = label_previous.rect_position.y label_current_base_position = label_current.rect_position.y @@ -105,7 +108,8 @@ func _gui_input(event: InputEvent): var drag := event as InputEventScreenDrag pointer.current_position = drag.position pointer.velocity = drag.speed - pointer.was_dragged = true + if pointer.current_position.distance_to(pointer.initial_position) / screen_dpcm > DRAG_THRESHOLD_CM: + pointer.was_dragged = true func input_text_entered(new_text: String): @@ -113,6 +117,7 @@ func input_text_entered(new_text: String): func input_focus_entered(): + pointer.velocity = Vector2.ZERO # Avoid changing to other value once entering input. input.text = "%d" % value input.visible = true input.select_all() diff --git a/icons/icon.png b/icons/icon.png index 63760ec..95c7db3 100755 Binary files a/icons/icon.png and b/icons/icon.png differ diff --git a/icons/icon.xcf b/icons/icon.xcf index abfbec1..57986fb 100644 Binary files a/icons/icon.xcf and b/icons/icon.xcf differ diff --git a/icons/icon_background.png b/icons/icon_background.png index 6ee62e1..bbb555d 100644 Binary files a/icons/icon_background.png and b/icons/icon_background.png differ diff --git a/logic/database.gd b/logic/database.gd index db449ae..5f91c12 100644 --- a/logic/database.gd +++ b/logic/database.gd @@ -24,6 +24,7 @@ const ENTRY_PROTOTYPE: Dictionary = { } var db: Array +var selected_idx: int var staged_idx: int onready var delete_button: Button = get_node("actions/delete") @@ -34,18 +35,14 @@ onready var stage: Stage = get_node("/root/main/stage") func _init(): + selected_idx = -1 staged_idx = -1 load_database() store_database() # @DAM Only for testing. -# @DAM WIP -#func _notification(what: int): -# if what == MainLoop.NOTIFICATION_WM_GO_BACK_REQUEST: -# clear_selection() - - func _ready(): + self.connect("item_selected", self, "item_selected") self.connect("nothing_selected", self, "clear_selection") delete_button.connect("pressed", self, "delete_action") edit_button.connect("pressed", self, "edit_action") @@ -56,31 +53,63 @@ func _ready(): self.add_item(get_entry_view(it)) +func _notification(what: int): + if what == MainLoop.NOTIFICATION_WM_GO_BACK_REQUEST: + if selected_idx >= 0: + clear_selection() + else: + get_tree().quit() + + +# @DAM Testing +#var last_event +#var bypass: bool = false +#func _gui_input(event: InputEvent): +# if bypass == false: +# if event is InputEventScreenTouch && event.is_pressed(): +# accept_event() +# last_event = event +# elif event is InputEventScreenTouch && event.is_pressed() == false: +# bypass = true +# get_tree().input_event(event) +# bypass = false +# else: +# var x = 3 + + func get_entry_view(database_entry: Dictionary) -> String: return "%6s | %6s | %s" % [database_entry.process_id, database_entry.surgery_id, get_entry_date(database_entry)] +func item_selected(index: int): + if selected_idx >= 0: + set_item_text(selected_idx, get_entry_view(db[selected_idx])) + selected_idx = index + set_item_text(selected_idx, "> " + get_entry_view(db[selected_idx]) + " <") + + + func clear_selection(): + if selected_idx >= 0: + set_item_text(selected_idx, get_entry_view(db[selected_idx])) + selected_idx = -1 unselect_all() func delete_action(): - var selected_entries_idx := self.get_selected_items() - if selected_entries_idx.size() == 0: + if selected_idx < 0: return - assert(selected_entries_idx.size() == 1, "Multiple selected items not supported.") - var selected_idx = selected_entries_idx[0] + db.remove(selected_idx) self.remove_item(selected_idx) store_database() func edit_action(): - var selected_entries_idx := self.get_selected_items() - if selected_entries_idx.size() == 0: + if selected_idx < 0: return - assert(selected_entries_idx.size() == 1, "Multiple selected items not supported.") - staged_idx = selected_entries_idx[0] + + staged_idx = selected_idx self.visible = false stage.visible = true stage.set_stage(db[staged_idx]) @@ -116,7 +145,7 @@ func load_database(file_path: String = DATABASE_FILE_PATH): file.open(file_path, File.READ_WRITE) var headers: PoolStringArray var is_first_line := true -# while database_file.eof_reached() == false: +# while database_file.eof_reached() == false: # @DAM Why this? while file.get_position() < file.get_len(): var csv_entry := file.get_csv_line() if is_first_line: @@ -135,7 +164,10 @@ func load_database(file_path: String = DATABASE_FILE_PATH): _: entry[field_name] = field_value db.append(entry) + + fake_database() return + db = [ instance_entry({ "process_id": "000001", @@ -191,7 +223,7 @@ static func instance_entry(params: Dictionary = {}) -> Dictionary: new_entry.date_year = params.get("date_year", today.year) new_entry.date_month = params.get("date_month", today.month) new_entry.date_day = params.get("date_day", today.day) - new_entry.date = params.get("date", get_entry_date(new_entry)) + new_entry.date = params.get("date", get_entry_date(new_entry)) # @DAM We should store only one version of the date. new_entry.anesthetic = params.get("anesthetic", "") new_entry.first_assistant = params.get("first_assistant", "") @@ -220,3 +252,76 @@ static func set_entry_date(entry: Dictionary, date: String): entry.date_day = int(date.substr(month_day_idx + 1)) +func fake_database(): + db.resize(0) + for idx in range(50): + var date_year = 1 + int(float(idx) / 365.0) + var date_month = idx % 12 + var date_day = idx % 365 + db.append(instance_entry({ + "process_id": "%06d" % idx, + "surgery_id": "s%05d" % idx, + "date": "%04d-%02d-%02d" % [date_year, date_month, date_day] + })) + + + + + + + +# @DAM Testing. +export(float) var drag_threshold = 5 + +onready var touch_enabled = OS.has_touchscreen_ui_hint() +onready var v_scroll = get_v_scroll() + +var dragging = false +var pressed = false +var last_event +# must be _input, as _gui_input has bugs, with no touch.pressed = false events +# see: https://github.com/godotengine/godot/issues/16761 +func _gui_input(event): + + if event is InputEventScreenDrag: + self.v_scroll.value -= event.relative.y + + return + if not visible: + return + + if not touch_enabled: + return + + if event is InputEventScreenDrag: + accept_event() + if event.speed == Vector2(): + # we're on a device and speed is broken + # see: https://github.com/godotengine/godot/issues/3623 + event.speed = event.relative + if abs(event.speed.y) >= drag_threshold: + # scroll the list + dragging = true + v_scroll.value -= event.relative.y + return + + if event is InputEventScreenTouch: + if event.index == 0: + accept_event() + if dragging && event.pressed == false: + # prints("end drag") + dragging = false + pressed = false + else: + if event.pressed && pressed == false: + # prints("touch start") + pressed = true + accept_event() + # TODO: prevent select highlight + elif !event.pressed && pressed == true: + # prints("touch end and accept") + pressed = false + var ev = InputEventAction.new() + ev.action = "ui_accept" + ev.pressed = true + Input.parse_input_event(ev) diff --git a/logic/stage.gd b/logic/stage.gd index 19018fa..207b451 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -1,4 +1,4 @@ -extends Control +extends ScrollContainer class_name Stage signal save # (database_entry: Dictionary) @@ -66,6 +66,7 @@ func get_stage() -> Dictionary: # "first_assistant": first_assistant.text, # "type": type.text, # }) + # @DAM Simplify all this... avoid creating multiple entries/dictionaries. var entry: Dictionary = { "process_id": process_id.text, "surgery_id": surgery_id.text, @@ -86,6 +87,14 @@ func get_stage() -> Dictionary: return entry +# @DAM Testing. Needs all children controllers to have Mouse > Filter : Pass. +func _gui_input(event): + accept_event() + if event is InputEventScreenDrag: + self.scroll_vertical -= event.relative.y + return + + func _notification(what: int): if what == MainLoop.NOTIFICATION_WM_GO_BACK_REQUEST: discard_action() diff --git a/main.tscn b/main.tscn index 53f083a..4096c59 100644 --- a/main.tscn +++ b/main.tscn @@ -116,6 +116,7 @@ margin_top = 112.0 margin_right = 1080.0 margin_bottom = 312.0 rect_min_size = Vector2( 400, 200 ) +mouse_filter = 0 [node name="place" type="LineEdit" parent="stage/controls"] margin_top = 316.0 diff --git a/project.godot b/project.godot index 584ffa8..3df8f21 100644 --- a/project.godot +++ b/project.godot @@ -19,7 +19,7 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://date_picker/date_picker.gd" }, { -"base": "Control", +"base": "ScrollContainer", "class": "Stage", "language": "GDScript", "path": "res://logic/stage.gd" diff --git a/touch_scroll.gd b/touch_scroll.gd new file mode 100644 index 0000000..ea92087 --- /dev/null +++ b/touch_scroll.gd @@ -0,0 +1,66 @@ +extends Control + + +func _gui_input(event: InputEvent): +#func _input(event: InputEvent): +# return + if event is InputEventScreenDrag: + self.v_scroll.value -= event.relative.y +# accept_event() + +#func _gui_input(event): +# accept_event() +# get_tree().set_input_as_handled() +# if event is InputEventScreenDrag: +# get_parent().v_scroll.value -= event.relative.y + + +export(float) var drag_threshold = 5 +onready var touch_enabled = OS.has_touchscreen_ui_hint() +onready var v_scroll = get_parent().get_v_scroll() + +var dragging = false +var pressed = false +var last_event +# must be _input, as _gui_input has bugs, with no touch.pressed = false events +# see: https://github.com/godotengine/godot/issues/16761 +func _input_disabled(event): +#func _gui_input(event): + + if not visible: + return + + if not touch_enabled: + return + + if event is InputEventScreenDrag: + accept_event() + if event.speed == Vector2(): + # we're on a device and speed is broken + # see: https://github.com/godotengine/godot/issues/3623 + event.speed = event.relative + if abs(event.speed.y) >= drag_threshold: + # scroll the list + dragging = true + v_scroll.value -= event.relative.y + return + + if event is InputEventScreenTouch: + if event.index == 0: + accept_event() + if dragging && event.pressed == false: + # prints("end drag") + dragging = false + pressed = false + else: + if event.pressed && pressed == false: + # prints("touch start") + pressed = true + # TODO: prevent select highlight + elif !event.pressed && pressed == true: + # prints("touch end and accept") + pressed = false + var ev = InputEventAction.new() + ev.action = "ui_accept" + ev.pressed = true + Input.parse_input_event(ev) -- cgit v1.2.3 From 5b863fdc25848817ef11f0c1a33ef45a4160aa2c Mon Sep 17 00:00:00 2001 From: dam Date: Mon, 27 Dec 2021 03:31:03 +0000 Subject: Allow to scroll database screen without selecting item on touch-down. --- fonts/font_regular.tres | 2 +- logic/database.gd | 95 ++++++---------------------------- logic/stage.gd | 8 --- main.tscn | 12 ++++- test_input.gd | 42 +++++++++++++++ test_input.tscn | 58 +++++++++++++++++++++ touch_scroll.gd | 134 ++++++++++++++++++++++++++++-------------------- 7 files changed, 207 insertions(+), 144 deletions(-) create mode 100644 test_input.gd create mode 100644 test_input.tscn (limited to 'logic/stage.gd') diff --git a/fonts/font_regular.tres b/fonts/font_regular.tres index 095114d..01b4567 100644 --- a/fonts/font_regular.tres +++ b/fonts/font_regular.tres @@ -3,6 +3,6 @@ [ext_resource path="res://fonts/B612Mono-Regular.ttf" type="DynamicFontData" id=1] [resource] -size = 34 +size = 32 use_filter = true font_data = ExtResource( 1 ) diff --git a/logic/database.gd b/logic/database.gd index 5f91c12..ea02324 100644 --- a/logic/database.gd +++ b/logic/database.gd @@ -54,6 +54,9 @@ func _ready(): func _notification(what: int): + if visible == false: + return + if what == MainLoop.NOTIFICATION_WM_GO_BACK_REQUEST: if selected_idx >= 0: clear_selection() @@ -61,22 +64,6 @@ func _notification(what: int): get_tree().quit() -# @DAM Testing -#var last_event -#var bypass: bool = false -#func _gui_input(event: InputEvent): -# if bypass == false: -# if event is InputEventScreenTouch && event.is_pressed(): -# accept_event() -# last_event = event -# elif event is InputEventScreenTouch && event.is_pressed() == false: -# bypass = true -# get_tree().input_event(event) -# bypass = false -# else: -# var x = 3 - - func get_entry_view(database_entry: Dictionary) -> String: return "%6s | %6s | %s" % [database_entry.process_id, database_entry.surgery_id, get_entry_date(database_entry)] @@ -102,6 +89,7 @@ func delete_action(): db.remove(selected_idx) self.remove_item(selected_idx) + selected_idx = -1 store_database() @@ -122,15 +110,25 @@ func add_action(): stage.set_stage(staged) +func scroll_down(): + get_v_scroll().value = get_v_scroll().max_value + + func save(database_entry: Dictionary): database_entry = instance_entry(database_entry) # @DAM Maybe we could not be creating endless dictionaries? + var next_selected_idx: int if staged_idx >= 0: db[staged_idx] = database_entry - set_item_text(staged_idx, get_entry_view(database_entry)) + next_selected_idx = staged_idx else: db.append(database_entry) add_item(get_entry_view(database_entry)) + call_deferred("scroll_down") + next_selected_idx = db.size() - 1 + store_database() + select(next_selected_idx) + item_selected(next_selected_idx) # Calling "select" does not trigger the "item_selected" signal. staged_idx = -1 self.visible = true @@ -254,7 +252,7 @@ static func set_entry_date(entry: Dictionary, date: String): func fake_database(): db.resize(0) - for idx in range(50): + for idx in range(500): var date_year = 1 + int(float(idx) / 365.0) var date_month = idx % 12 var date_day = idx % 365 @@ -264,64 +262,3 @@ func fake_database(): "date": "%04d-%02d-%02d" % [date_year, date_month, date_day] })) - - - - - - -# @DAM Testing. -export(float) var drag_threshold = 5 - -onready var touch_enabled = OS.has_touchscreen_ui_hint() -onready var v_scroll = get_v_scroll() - -var dragging = false -var pressed = false -var last_event -# must be _input, as _gui_input has bugs, with no touch.pressed = false events -# see: https://github.com/godotengine/godot/issues/16761 -func _gui_input(event): - - if event is InputEventScreenDrag: - self.v_scroll.value -= event.relative.y - - return - if not visible: - return - - if not touch_enabled: - return - - if event is InputEventScreenDrag: - accept_event() - if event.speed == Vector2(): - # we're on a device and speed is broken - # see: https://github.com/godotengine/godot/issues/3623 - event.speed = event.relative - if abs(event.speed.y) >= drag_threshold: - # scroll the list - dragging = true - v_scroll.value -= event.relative.y - return - - if event is InputEventScreenTouch: - if event.index == 0: - accept_event() - if dragging && event.pressed == false: - # prints("end drag") - dragging = false - pressed = false - else: - if event.pressed && pressed == false: - # prints("touch start") - pressed = true - accept_event() - # TODO: prevent select highlight - elif !event.pressed && pressed == true: - # prints("touch end and accept") - pressed = false - var ev = InputEventAction.new() - ev.action = "ui_accept" - ev.pressed = true - Input.parse_input_event(ev) diff --git a/logic/stage.gd b/logic/stage.gd index 207b451..ac5e9aa 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -100,11 +100,3 @@ func _notification(what: int): discard_action() -func _unhandled_input(event): - get_node("/root/main/debug").text += "%s\n" % event.to_string() - - -func _unhandled_key_input(event): - get_node("/root/main/debug").text += "%s\n" % event.to_string() - - diff --git a/main.tscn b/main.tscn index 4096c59..b584c88 100644 --- a/main.tscn +++ b/main.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=10 format=2] +[gd_scene load_steps=11 format=2] [ext_resource path="res://main.gd" type="Script" id=1] [ext_resource path="res://date_picker/date_picker.tscn" type="PackedScene" id=2] @@ -9,6 +9,7 @@ [ext_resource path="res://icons/add.png" type="Texture" id=7] [ext_resource path="res://icons/delete.png" type="Texture" id=8] [ext_resource path="res://logic/stage.gd" type="Script" id=9] +[ext_resource path="res://touch_scroll.gd" type="Script" id=10] [node name="main" type="Control"] anchor_right = 1.0 @@ -28,6 +29,15 @@ __meta__ = { "_edit_use_anchors_": false } +[node name="touch_scroll" type="Control" parent="database"] +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_right = -8.0 +script = ExtResource( 10 ) +__meta__ = { +"_edit_use_anchors_": false +} + [node name="actions" type="VBoxContainer" parent="database"] anchor_left = 1.0 anchor_top = 1.0 diff --git a/test_input.gd b/test_input.gd new file mode 100644 index 0000000..bae409d --- /dev/null +++ b/test_input.gd @@ -0,0 +1,42 @@ +extends ColorRect + +onready var debug: RichTextLabel = get_node("/root/main/debug") + + +func _gui_input(event): +# if name == "a": +# accept_event() +# if name == "b" : #&& (event is InputEventScreenTouch): +# if name == "b": +# get_tree().set_input_as_handled() +# accept_event() +# if name == "b" && event is InputEventScreenTouch && event.is_pressed() == false: +# simulate_click() +# call_deferred("simulate_click") + + if event is InputEventScreenTouch || event is InputEventMouseButton: + debug.text += "%s> %-24s\t:\t%s\n" % [name, event.get_class(), event.is_pressed()] + else: + debug.text += "%s> %s\n" % [name, event.get_class()] + + +func simulate_click(): + var event_touch = InputEventScreenTouch.new() + event_touch.index = 0 + event_touch.position = self.get_global_mouse_position() + + var event_mouse = InputEventMouseButton.new() + event_mouse.button_index = BUTTON_LEFT + event_mouse.button_mask = BUTTON_MASK_LEFT + event_mouse.position = self.get_global_mouse_position() + + self.mouse_filter = Control.MOUSE_FILTER_IGNORE + event_mouse.pressed = true + event_touch.pressed = true +# Input.parse_input_event(event_mouse) + Input.parse_input_event(event_touch) + event_mouse.pressed = false + event_touch.pressed = false +# Input.parse_input_event(event_mouse) + Input.parse_input_event(event_touch) + self.mouse_filter = Control.MOUSE_FILTER_STOP diff --git a/test_input.tscn b/test_input.tscn new file mode 100644 index 0000000..4b507fe --- /dev/null +++ b/test_input.tscn @@ -0,0 +1,58 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://test_input.gd" type="Script" id=1] +[ext_resource path="res://fonts/font_regular.tres" type="DynamicFont" id=2] + +[node name="main" type="Control"] +anchor_right = 1.0 +anchor_bottom = 1.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="debug" type="RichTextLabel" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +mouse_filter = 2 +custom_fonts/normal_font = ExtResource( 2 ) +scroll_following = true +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="a" type="ColorRect" parent="."] +anchor_right = 0.66 +anchor_bottom = 0.66 +color = Color( 1, 0, 0, 0.196078 ) +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="b" type="ColorRect" parent="a"] +anchor_left = 0.5 +anchor_right = 1.502 +anchor_bottom = 1.0 +mouse_filter = 1 +color = Color( 0, 1, 0, 0.196078 ) +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="c" type="ColorRect" parent="."] +anchor_top = 0.333 +anchor_right = 0.666 +anchor_bottom = 1.0 +color = Color( 0, 0, 1, 0.196078 ) +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="d" type="ColorRect" parent="c"] +anchor_left = 0.5 +anchor_right = 1.502 +anchor_bottom = 1.0 +color = Color( 1, 1, 1, 0.196078 ) +script = ExtResource( 1 ) diff --git a/touch_scroll.gd b/touch_scroll.gd index ea92087..f2aeca7 100644 --- a/touch_scroll.gd +++ b/touch_scroll.gd @@ -1,66 +1,90 @@ extends Control +const VELOCITY_DECAYING_FACTOR: float = 5.5 +const DRAG_THRESHOLD_CM: float = 0.250 -func _gui_input(event: InputEvent): -#func _input(event: InputEvent): -# return - if event is InputEventScreenDrag: - self.v_scroll.value -= event.relative.y -# accept_event() +var pointer: Dictionary +var anchor: float +var offset: float +var screen_dpcm: float + +onready var target: Control = get_parent() +onready var target_scroll: VScrollBar = get_parent().get_v_scroll() + + +func _ready(): + pointer = { + index = -1, + initial_position = Vector2.ZERO, + current_position = Vector2.ZERO, + velocity = Vector2.ZERO, + was_dragged = false, + is_active = false, + } + screen_dpcm = float(OS.get_screen_dpi()) / 2.54 -#func _gui_input(event): -# accept_event() -# get_tree().set_input_as_handled() -# if event is InputEventScreenDrag: -# get_parent().v_scroll.value -= event.relative.y +func _process(delta: float): + if pointer.is_active: + var dragged_distance: float = - (pointer.current_position.y - pointer.initial_position.y) + offset = anchor + dragged_distance + self.target_scroll.value = offset + elif pointer.velocity.length() > 0.5: + pointer.velocity *= clamp((1.0 - VELOCITY_DECAYING_FACTOR * delta), 0.0, 1.0) + offset -= pointer.velocity.y * delta + self.target_scroll.value = offset -export(float) var drag_threshold = 5 -onready var touch_enabled = OS.has_touchscreen_ui_hint() -onready var v_scroll = get_parent().get_v_scroll() -var dragging = false -var pressed = false -var last_event -# must be _input, as _gui_input has bugs, with no touch.pressed = false events -# see: https://github.com/godotengine/godot/issues/16761 -func _input_disabled(event): -#func _gui_input(event): +func _gui_input(event: InputEvent): + + # @DAM A bug on GODOT-3.X makes events from non-mouse-emulated pointers (index > 0) unreliable. + if event is InputEventScreenTouch || event is InputEventScreenDrag: + if event.index != 0: + return + + if event is InputEventScreenTouch && (pointer.is_active == false || pointer.index == event.index): + var touch := event as InputEventScreenTouch + pointer.is_active = event.pressed + pointer.current_position = touch.position + if pointer.is_active: + pointer.index = touch.index + pointer.initial_position = touch.position + anchor = self.target_scroll.value + else: + if pointer.was_dragged == false: # Click detected. + simulate_gui_click() + pointer.index = -1 + pointer.was_dragged = false - if not visible: - return + if event is InputEventScreenDrag && event.index == pointer.index: + var drag := event as InputEventScreenDrag + pointer.current_position = drag.position + pointer.velocity = drag.speed + if pointer.current_position.distance_to(pointer.initial_position) / screen_dpcm > DRAG_THRESHOLD_CM: + pointer.was_dragged = true - if not touch_enabled: - return - if event is InputEventScreenDrag: - accept_event() - if event.speed == Vector2(): - # we're on a device and speed is broken - # see: https://github.com/godotengine/godot/issues/3623 - event.speed = event.relative - if abs(event.speed.y) >= drag_threshold: - # scroll the list - dragging = true - v_scroll.value -= event.relative.y - return +func simulate_gui_click(): + + var position := self.get_global_mouse_position() - target.rect_global_position + + var event_touch := InputEventScreenTouch.new() + event_touch.index = 0 + event_touch.position = position + + var event_mouse := InputEventMouseButton.new() + event_mouse.button_index = BUTTON_LEFT + event_mouse.button_mask = BUTTON_MASK_LEFT + event_mouse.position = position + + event_mouse.pressed = true + event_touch.pressed = true + target._gui_input(event_mouse) + target._gui_input(event_touch) + + event_mouse.pressed = false + event_touch.pressed = false + target._gui_input(event_mouse) + target._gui_input(event_touch) + - if event is InputEventScreenTouch: - if event.index == 0: - accept_event() - if dragging && event.pressed == false: - # prints("end drag") - dragging = false - pressed = false - else: - if event.pressed && pressed == false: - # prints("touch start") - pressed = true - # TODO: prevent select highlight - elif !event.pressed && pressed == true: - # prints("touch end and accept") - pressed = false - var ev = InputEventAction.new() - ev.action = "ui_accept" - ev.pressed = true - Input.parse_input_event(ev) -- cgit v1.2.3 From 7f0580b6b49a9dbafc54a0190bf573503c54417a Mon Sep 17 00:00:00 2001 From: dam Date: Fri, 31 Dec 2021 15:41:10 +0000 Subject: Prototype implementation of touch scroll on database and stage screens. --- date_picker/date_picker.tscn | 1 - logic/database.gd | 7 +++-- logic/stage.gd | 47 ++++++++++++++++++++++++++++---- main.tscn | 25 ++++++++--------- project.godot | 6 ++++ touch_scroll.gd | 65 ++++++++++++++++++++++++++++++++++---------- 6 files changed, 114 insertions(+), 37 deletions(-) (limited to 'logic/stage.gd') diff --git a/date_picker/date_picker.tscn b/date_picker/date_picker.tscn index 21573dc..54b2b82 100644 --- a/date_picker/date_picker.tscn +++ b/date_picker/date_picker.tscn @@ -6,7 +6,6 @@ [node name="date_picker" type="Control"] anchor_right = 1.0 anchor_bottom = 1.0 -mouse_filter = 2 script = ExtResource( 2 ) __meta__ = { "_edit_use_anchors_": false diff --git a/logic/database.gd b/logic/database.gd index ea02324..4f3e613 100644 --- a/logic/database.gd +++ b/logic/database.gd @@ -27,11 +27,11 @@ var db: Array var selected_idx: int var staged_idx: int +onready var stage: Stage = get_node("/root/main/stage") onready var delete_button: Button = get_node("actions/delete") onready var edit_button: Button = get_node("actions/edit") onready var add_button: Button = get_node("actions/add") - -onready var stage: Stage = get_node("/root/main/stage") +onready var v_scroll_bar: ScrollBar = get_v_scroll() func _init(): @@ -111,7 +111,7 @@ func add_action(): func scroll_down(): - get_v_scroll().value = get_v_scroll().max_value + v_scroll_bar.value = v_scroll_bar.max_value func save(database_entry: Dictionary): @@ -129,6 +129,7 @@ func save(database_entry: Dictionary): store_database() select(next_selected_idx) item_selected(next_selected_idx) # Calling "select" does not trigger the "item_selected" signal. + v_scroll_bar.value = float(next_selected_idx)/float(db.size()) * v_scroll_bar.max_value - (v_scroll_bar.page * 0.5) staged_idx = -1 self.visible = true diff --git a/logic/stage.gd b/logic/stage.gd index ac5e9aa..f7cced4 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -4,6 +4,22 @@ class_name Stage signal save # (database_entry: Dictionary) signal discard # () +#onready var process_id: LineEdit = get_node("touch_scroll/controls/process_id") +#onready var surgery_id: LineEdit = get_node("touch_scroll/controls/surgery_id") +#onready var date: DatePicker = get_node("touch_scroll/controls/date_picker") +#onready var place: LineEdit = get_node("touch_scroll/controls/place") +#onready var anesthetic: LineEdit = get_node("touch_scroll/controls/anesthetic") +#onready var first_assistant: LineEdit = get_node("touch_scroll/controls/first_assistant") +#onready var type: LineEdit = get_node("touch_scroll/controls/type") +#onready var sub_type: LineEdit = get_node("touch_scroll/controls/sub_type") +#onready var sub_sub_type: LineEdit = get_node("touch_scroll/controls/sub_sub_type") +#onready var pathology: LineEdit = get_node("touch_scroll/controls/pathology") +#onready var intervention: LineEdit = get_node("touch_scroll/controls/intervention") +#onready var is_urgency: Button = get_node("touch_scroll/controls/is_urgency") +#onready var notes: LineEdit = get_node("touch_scroll/controls/notes") +#onready var save_button: Button = get_node("touch_scroll/controls/save") +#onready var discard_button: Button = get_node("touch_scroll/controls/discard") + onready var process_id: LineEdit = get_node("controls/process_id") onready var surgery_id: LineEdit = get_node("controls/surgery_id") onready var date: DatePicker = get_node("controls/date_picker") @@ -21,9 +37,30 @@ onready var save_button: Button = get_node("controls/save") onready var discard_button: Button = get_node("controls/discard") + func _ready(): save_button.connect("pressed", self, "save_action") discard_button.connect("pressed", self, "discard_action") + + + for it in get_node("controls").get_children(): +# print("%s" % it.name) + match it.name: + "date_picker", "save", "discard": + print("- %s" % it.name) + + _: +# "first_assistant": + print("+ %s" % it.name) + var touch_scroll = TouchScroll.new() + touch_scroll.name = "touch_scroll" + touch_scroll.click_target_path = "../" + touch_scroll.scroll_target_path = "../../../" + touch_scroll.scroll_bar_get_method = "get_v_scrollbar" + touch_scroll.mouse_default_cursor_shape = Control.CURSOR_IBEAM + (it as Control).add_child(touch_scroll) + touch_scroll.anchor_right = 1.0 + touch_scroll.anchor_bottom = 1.0 func save_action(): @@ -88,11 +125,11 @@ func get_stage() -> Dictionary: # @DAM Testing. Needs all children controllers to have Mouse > Filter : Pass. -func _gui_input(event): - accept_event() - if event is InputEventScreenDrag: - self.scroll_vertical -= event.relative.y - return +#func _gui_input(event): +# accept_event() +# if event is InputEventScreenDrag: +# self.scroll_vertical -= event.relative.y +# return func _notification(what: int): diff --git a/main.tscn b/main.tscn index b584c88..9cb9220 100644 --- a/main.tscn +++ b/main.tscn @@ -37,6 +37,9 @@ script = ExtResource( 10 ) __meta__ = { "_edit_use_anchors_": false } +click_target_path = "../" +scroll_target_path = "../" +scroll_bar_get_method = "get_v_scroll" [node name="actions" type="VBoxContainer" parent="database"] anchor_left = 1.0 @@ -100,9 +103,6 @@ margin_right = 1080.0 margin_bottom = 1860.0 size_flags_horizontal = 3 size_flags_vertical = 3 -__meta__ = { -"_edit_use_anchors_": false -} [node name="process_id" type="LineEdit" parent="stage/controls"] margin_right = 1080.0 @@ -126,7 +126,6 @@ margin_top = 112.0 margin_right = 1080.0 margin_bottom = 312.0 rect_min_size = Vector2( 400, 200 ) -mouse_filter = 0 [node name="place" type="LineEdit" parent="stage/controls"] margin_top = 316.0 @@ -206,33 +205,33 @@ placeholder_text = "Notas" caret_blink = true caret_blink_speed = 0.5 -[node name="save" type="Button" parent="stage/controls"] -margin_top = 874.0 +[node name="discard" type="Button" parent="stage/controls"] +margin_top = 928.0 margin_right = 1080.0 -margin_bottom = 924.0 +margin_bottom = 978.0 grow_horizontal = 2 grow_vertical = 2 rect_min_size = Vector2( 0, 50 ) -text = "save" +text = "discard" __meta__ = { "_edit_use_anchors_": false } -[node name="discard" type="Button" parent="stage/controls"] -margin_top = 928.0 +[node name="save" type="Button" parent="stage/controls"] +margin_top = 874.0 margin_right = 1080.0 -margin_bottom = 978.0 +margin_bottom = 924.0 grow_horizontal = 2 grow_vertical = 2 rect_min_size = Vector2( 0, 50 ) -text = "discard" +text = "save" __meta__ = { "_edit_use_anchors_": false } [node name="menu" type="MenuButton" parent="."] anchor_right = 1.0 -margin_bottom = 48.0 +margin_bottom = 60.0 text = "≡" align = 2 script = ExtResource( 4 ) diff --git a/project.godot b/project.godot index 3df8f21..703d936 100644 --- a/project.godot +++ b/project.godot @@ -25,6 +25,11 @@ _global_script_classes=[ { "path": "res://logic/stage.gd" }, { "base": "Control", +"class": "TouchScroll", +"language": "GDScript", +"path": "res://touch_scroll.gd" +}, { +"base": "Control", "class": "ValuePicker", "language": "GDScript", "path": "res://date_picker/value_picker.gd" @@ -33,6 +38,7 @@ _global_script_class_icons={ "Database": "", "DatePicker": "", "Stage": "", +"TouchScroll": "", "ValuePicker": "" } diff --git a/touch_scroll.gd b/touch_scroll.gd index f2aeca7..7b6aefd 100644 --- a/touch_scroll.gd +++ b/touch_scroll.gd @@ -1,22 +1,45 @@ extends Control +class_name TouchScroll -const VELOCITY_DECAYING_FACTOR: float = 5.5 const DRAG_THRESHOLD_CM: float = 0.250 +export var click_target_path: String +export var scroll_target_path: String +export var scroll_bar_get_method: String +export var scroll_velocity_decaying_factor: float = 2.5 + var pointer: Dictionary var anchor: float var offset: float var screen_dpcm: float -onready var target: Control = get_parent() -onready var target_scroll: VScrollBar = get_parent().get_v_scroll() +var click_target: Control +var scroll_target: Control +var target_scroll_bar: VScrollBar + + + +# @DAM +# Change this into an event-driven approach by changing the touch_scroll into a signal emitter and +# the target scrollable control a signal consumer. This way, the signal consumer will behave as the +# central node and will have the capability to detect when different touch_scroll sensors are +# activated and stop previous inputs (if required). +# Signal emitters should signal: +# - when an input drag starts +# - when an input drag stops +# - when an input drags +# - when an input clicks func _ready(): + click_target = get_node(click_target_path) + scroll_target = get_node(scroll_target_path) + target_scroll_bar = scroll_target.call(scroll_bar_get_method) pointer = { index = -1, initial_position = Vector2.ZERO, current_position = Vector2.ZERO, + relative_position = Vector2.ZERO, velocity = Vector2.ZERO, was_dragged = false, is_active = false, @@ -25,14 +48,23 @@ func _ready(): func _process(delta: float): + # @DAM This only works for the stage... for the database this hides the touch_scroll after the first click. + if click_target.has_focus(): + mouse_filter = Control.MOUSE_FILTER_IGNORE + else: + mouse_filter = Control.MOUSE_FILTER_STOP + if pointer.is_active: var dragged_distance: float = - (pointer.current_position.y - pointer.initial_position.y) offset = anchor + dragged_distance - self.target_scroll.value = offset +# self.target_scroll_bar.value = offset + self.target_scroll_bar.value -= pointer.relative_position.y + pointer.relative_position = Vector2.ZERO elif pointer.velocity.length() > 0.5: - pointer.velocity *= clamp((1.0 - VELOCITY_DECAYING_FACTOR * delta), 0.0, 1.0) + pointer.velocity *= clamp((1.0 - scroll_velocity_decaying_factor * delta), 0.0, 1.0) offset -= pointer.velocity.y * delta - self.target_scroll.value = offset +# self.target_scroll_bar.value = offset + self.target_scroll_bar.value -= pointer.velocity.y * delta func _gui_input(event: InputEvent): @@ -45,11 +77,11 @@ func _gui_input(event: InputEvent): if event is InputEventScreenTouch && (pointer.is_active == false || pointer.index == event.index): var touch := event as InputEventScreenTouch pointer.is_active = event.pressed - pointer.current_position = touch.position + pointer.current_position = get_global_mouse_position() # touch.position if pointer.is_active: pointer.index = touch.index - pointer.initial_position = touch.position - anchor = self.target_scroll.value + pointer.initial_position = get_global_mouse_position() # touch.position + anchor = self.target_scroll_bar.value else: if pointer.was_dragged == false: # Click detected. simulate_gui_click() @@ -58,15 +90,16 @@ func _gui_input(event: InputEvent): if event is InputEventScreenDrag && event.index == pointer.index: var drag := event as InputEventScreenDrag - pointer.current_position = drag.position + pointer.current_position = get_global_mouse_position() # drag.position pointer.velocity = drag.speed + pointer.relative_position = drag.relative if pointer.current_position.distance_to(pointer.initial_position) / screen_dpcm > DRAG_THRESHOLD_CM: pointer.was_dragged = true func simulate_gui_click(): - var position := self.get_global_mouse_position() - target.rect_global_position + var position := self.get_global_mouse_position() - click_target.rect_global_position var event_touch := InputEventScreenTouch.new() event_touch.index = 0 @@ -79,12 +112,14 @@ func simulate_gui_click(): event_mouse.pressed = true event_touch.pressed = true - target._gui_input(event_mouse) - target._gui_input(event_touch) + click_target._gui_input(event_mouse) + click_target._gui_input(event_touch) + click_target.grab_focus() + event_mouse.pressed = false event_touch.pressed = false - target._gui_input(event_mouse) - target._gui_input(event_touch) + click_target._gui_input(event_mouse) + click_target._gui_input(event_touch) -- cgit v1.2.3 From c59f89e99c90c0e756cdf780bcbf04d782eb3e6f Mon Sep 17 00:00:00 2001 From: dam Date: Sat, 1 Jan 2022 04:56:58 +0000 Subject: Change touch scroll detection to pointer-input-sensor approach using signals/events. --- logic/database.gd | 60 ++++++++++++++++++++++- logic/stage.gd | 104 ++++++++++++++++++++++++++++------------ main.tscn | 7 +-- pointer_input_sensor.gd | 84 ++++++++++++++++++++++++++++++++ project.godot | 12 ++--- touch_scroll.gd | 125 ------------------------------------------------ 6 files changed, 224 insertions(+), 168 deletions(-) create mode 100644 pointer_input_sensor.gd delete mode 100644 touch_scroll.gd (limited to 'logic/stage.gd') diff --git a/logic/database.gd b/logic/database.gd index 4f3e613..41ba679 100644 --- a/logic/database.gd +++ b/logic/database.gd @@ -31,7 +31,8 @@ onready var stage: Stage = get_node("/root/main/stage") onready var delete_button: Button = get_node("actions/delete") onready var edit_button: Button = get_node("actions/edit") onready var add_button: Button = get_node("actions/add") -onready var v_scroll_bar: ScrollBar = get_v_scroll() +onready var v_scroll_bar: ScrollBar = get_v_scroll() # @DAM Stop scroll inertia when buttons are pressed. +onready var drag_sensor: PointerInputSensor = get_node("drag_sensor") func _init(): @@ -44,15 +45,72 @@ func _init(): func _ready(): self.connect("item_selected", self, "item_selected") self.connect("nothing_selected", self, "clear_selection") + delete_button.connect("pressed", self, "delete_action") edit_button.connect("pressed", self, "edit_action") add_button.connect("pressed", self, "add_action") + stage.connect("save", self, "save") stage.connect("discard", self, "discard") + + drag_sensor.connect("on_press", self, "on_press") + drag_sensor.connect("on_drag", self, "on_drag") + drag_sensor.connect("on_end_drag", self, "on_end_drag") + drag_sensor.connect("on_click", self, "on_click") + for it in db: self.add_item(get_entry_view(it)) +# @DAM Cleanup this code. +export var scroll_velocity_decaying_factor: float = 2.5 +var drag_velocity := 0.0 +var is_dragging := false + +func _process(delta: float): + if is_dragging == false && abs(drag_velocity) > 0.5: + drag_velocity *= clamp((1.0 - scroll_velocity_decaying_factor * delta), 0.0, 1.0) + v_scroll_bar.value -= drag_velocity * delta + +func on_click(pointer: PointerInputSensor.PointerInputData): + var target := self + var position := target.get_global_mouse_position() - target.rect_global_position + + var event_touch := InputEventScreenTouch.new() + event_touch.index = 0 + event_touch.position = position + + var event_mouse := InputEventMouseButton.new() + event_mouse.button_index = BUTTON_LEFT + event_mouse.button_mask = BUTTON_MASK_LEFT + event_mouse.position = position + + event_mouse.pressed = true + event_touch.pressed = true + target._gui_input(event_mouse) + target._gui_input(event_touch) + + target.grab_focus() + + event_mouse.pressed = false + event_touch.pressed = false + target._gui_input(event_mouse) + target._gui_input(event_touch) + +func on_press(pointer: PointerInputSensor.PointerInputData): + is_dragging = true + +func on_end_drag(pointer: PointerInputSensor.PointerInputData): + is_dragging = false + +func on_drag(pointer: PointerInputSensor.PointerInputData): + is_dragging = true + drag_velocity = pointer.velocity.y + v_scroll_bar.value -= pointer.relative_position.y + + + + func _notification(what: int): if visible == false: return diff --git a/logic/stage.gd b/logic/stage.gd index f7cced4..62e0a58 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -37,7 +37,6 @@ onready var save_button: Button = get_node("controls/save") onready var discard_button: Button = get_node("controls/discard") - func _ready(): save_button.connect("pressed", self, "save_action") discard_button.connect("pressed", self, "discard_action") @@ -48,19 +47,82 @@ func _ready(): match it.name: "date_picker", "save", "discard": print("- %s" % it.name) - + _: # "first_assistant": print("+ %s" % it.name) - var touch_scroll = TouchScroll.new() - touch_scroll.name = "touch_scroll" - touch_scroll.click_target_path = "../" - touch_scroll.scroll_target_path = "../../../" - touch_scroll.scroll_bar_get_method = "get_v_scrollbar" - touch_scroll.mouse_default_cursor_shape = Control.CURSOR_IBEAM - (it as Control).add_child(touch_scroll) - touch_scroll.anchor_right = 1.0 - touch_scroll.anchor_bottom = 1.0 + var drag_sensor = PointerInputSensor.new() + drag_sensor.name = "drag_sensor" + drag_sensor.mouse_default_cursor_shape = Control.CURSOR_IBEAM + (it as Control).add_child(drag_sensor) + drag_sensor.anchor_right = 1.0 + drag_sensor.anchor_bottom = 1.0 + + drag_sensor.connect("on_press", self, "on_press") + drag_sensor.connect("on_drag", self, "on_drag") + drag_sensor.connect("on_end_drag", self, "on_end_drag") + drag_sensor.connect("on_click", self, "on_click") + drag_sensor.on_process = funcref(self, "bazinga") + + +# @DAM Cleanup this code. +onready var v_scroll_bar: VScrollBar = get_v_scrollbar() +export var scroll_velocity_decaying_factor: float = 2.5 +var drag_velocity := 0.0 +var is_dragging := false + +func bazinga(delta: float, pointer: PointerInputSensor): + if (pointer.get_parent() as Control).has_focus(): + pointer.mouse_filter = Control.MOUSE_FILTER_IGNORE + else: + pointer.mouse_filter = Control.MOUSE_FILTER_STOP + +func _process(delta: float): + if is_dragging == false && abs(drag_velocity) > 0.5: + drag_velocity *= clamp((1.0 - scroll_velocity_decaying_factor * delta), 0.0, 1.0) + v_scroll_bar.value -= drag_velocity * delta + +func on_click(pointer: PointerInputSensor.PointerInputData): + var target: Control = pointer.target.get_parent() + var position := target.get_global_mouse_position() - target.rect_global_position + + var event_touch := InputEventScreenTouch.new() + event_touch.index = 0 + event_touch.position = position + + var event_mouse := InputEventMouseButton.new() + event_mouse.button_index = BUTTON_LEFT + event_mouse.button_mask = BUTTON_MASK_LEFT + event_mouse.position = position + + event_mouse.pressed = true + event_touch.pressed = true + target._gui_input(event_mouse) + target._gui_input(event_touch) + + target.grab_focus() + + event_mouse.pressed = false + event_touch.pressed = false + target._gui_input(event_mouse) + target._gui_input(event_touch) + +func on_press(pointer: PointerInputSensor.PointerInputData): + is_dragging = true + +func on_end_drag(pointer: PointerInputSensor.PointerInputData): + is_dragging = false + +func on_drag(pointer: PointerInputSensor.PointerInputData): + is_dragging = true + drag_velocity = pointer.velocity.y + v_scroll_bar.value -= pointer.relative_position.y + + + + + + func save_action(): @@ -91,18 +153,6 @@ func set_stage(entry: Dictionary): func get_stage() -> Dictionary: - -# var entry: Dictionary = Database.instance_entry({ -# "process_id": process_id.text, -# "surgery_id": surgery_id.text, -# "date_year": date.get_year(), -# "date_month": date.get_month(), -# "date_day": date.get_day(), -# "place": place.text, -# "anesthetic": anesthetic.text, -# "first_assistant": first_assistant.text, -# "type": type.text, -# }) # @DAM Simplify all this... avoid creating multiple entries/dictionaries. var entry: Dictionary = { "process_id": process_id.text, @@ -124,14 +174,6 @@ func get_stage() -> Dictionary: return entry -# @DAM Testing. Needs all children controllers to have Mouse > Filter : Pass. -#func _gui_input(event): -# accept_event() -# if event is InputEventScreenDrag: -# self.scroll_vertical -= event.relative.y -# return - - func _notification(what: int): if what == MainLoop.NOTIFICATION_WM_GO_BACK_REQUEST: discard_action() diff --git a/main.tscn b/main.tscn index 9cb9220..331d94d 100644 --- a/main.tscn +++ b/main.tscn @@ -9,7 +9,7 @@ [ext_resource path="res://icons/add.png" type="Texture" id=7] [ext_resource path="res://icons/delete.png" type="Texture" id=8] [ext_resource path="res://logic/stage.gd" type="Script" id=9] -[ext_resource path="res://touch_scroll.gd" type="Script" id=10] +[ext_resource path="res://pointer_input_sensor.gd" type="Script" id=10] [node name="main" type="Control"] anchor_right = 1.0 @@ -29,7 +29,7 @@ __meta__ = { "_edit_use_anchors_": false } -[node name="touch_scroll" type="Control" parent="database"] +[node name="drag_sensor" type="Control" parent="database"] anchor_right = 1.0 anchor_bottom = 1.0 margin_right = -8.0 @@ -37,9 +37,6 @@ script = ExtResource( 10 ) __meta__ = { "_edit_use_anchors_": false } -click_target_path = "../" -scroll_target_path = "../" -scroll_bar_get_method = "get_v_scroll" [node name="actions" type="VBoxContainer" parent="database"] anchor_left = 1.0 diff --git a/pointer_input_sensor.gd b/pointer_input_sensor.gd new file mode 100644 index 0000000..95e1b4b --- /dev/null +++ b/pointer_input_sensor.gd @@ -0,0 +1,84 @@ +extends Control +class_name PointerInputSensor + +export var drag_threshold_cm: float = 0.250 + +signal on_press +signal on_release +#signal on_release_outside +signal on_click +signal on_enter +signal on_exit +#signal on_exit_app_window +signal on_begin_drag +signal on_drag +signal on_end_drag + +class PointerInputData: + var target: PointerInputSensor + var index := -1 + var initial_position := Vector2.ZERO + var current_position := Vector2.ZERO + var relative_position = Vector2.ZERO + var velocity := Vector2.ZERO + var was_dragged := false + var is_pressed := false + +var pointer: PointerInputData +var screen_dpcm: float +var on_process: FuncRef + + +func _ready(): + screen_dpcm = float(OS.get_screen_dpi()) / 2.54 + pointer = PointerInputData.new() + pointer.target = self + connect("mouse_entered", self, "_on_enter_exit", [true]) + connect("mouse_entered", self, "_on_enter_exit", [false]) + + +func _process(delta: float): + if on_process != null && on_process.is_valid(): + on_process.call_func(delta, self) + + +func _on_enter_exit(is_inside: bool): + emit_signal("on_enter" if is_inside else "on_exit", pointer) + + +func _gui_input(event: InputEvent): + + # @DAM A bug on GODOT-3.X makes events from non-mouse-emulated pointers (index > 0) unreliable. + if event is InputEventScreenTouch || event is InputEventScreenDrag: + if event.index != 0: + return + + if event is InputEventScreenTouch && (pointer.is_pressed == false || pointer.index == event.index): + var touch := event as InputEventScreenTouch + pointer.is_pressed = event.pressed + pointer.current_position = get_global_mouse_position() + if pointer.is_pressed: + pointer.index = touch.index + pointer.initial_position = get_global_mouse_position() + emit_signal("on_press", pointer) + else: + emit_signal("on_release", pointer) + if pointer.was_dragged == false: + emit_signal("on_click", pointer) + else: + emit_signal("on_end_drag", pointer) + pointer.index = -1 + pointer.was_dragged = false + + if event is InputEventScreenDrag && event.index == pointer.index: + var drag := event as InputEventScreenDrag + pointer.current_position = get_global_mouse_position() + pointer.velocity = drag.speed + pointer.relative_position = drag.relative + # @DAM Instead of constantly converting the pointer distance to cm... just conver the drag_threshold_cm to pixels + if pointer.was_dragged == false && pointer.current_position.distance_to(pointer.initial_position) / screen_dpcm > drag_threshold_cm: + pointer.was_dragged = true + emit_signal("on_begin_drag", pointer) + emit_signal("on_drag", pointer) + + diff --git a/project.godot b/project.godot index 703d936..f5dbe7a 100644 --- a/project.godot +++ b/project.godot @@ -19,17 +19,17 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://date_picker/date_picker.gd" }, { +"base": "Control", +"class": "PointerInputSensor", +"language": "GDScript", +"path": "res://pointer_input_sensor.gd" +}, { "base": "ScrollContainer", "class": "Stage", "language": "GDScript", "path": "res://logic/stage.gd" }, { "base": "Control", -"class": "TouchScroll", -"language": "GDScript", -"path": "res://touch_scroll.gd" -}, { -"base": "Control", "class": "ValuePicker", "language": "GDScript", "path": "res://date_picker/value_picker.gd" @@ -37,8 +37,8 @@ _global_script_classes=[ { _global_script_class_icons={ "Database": "", "DatePicker": "", +"PointerInputSensor": "", "Stage": "", -"TouchScroll": "", "ValuePicker": "" } diff --git a/touch_scroll.gd b/touch_scroll.gd deleted file mode 100644 index 7b6aefd..0000000 --- a/touch_scroll.gd +++ /dev/null @@ -1,125 +0,0 @@ -extends Control -class_name TouchScroll - -const DRAG_THRESHOLD_CM: float = 0.250 - -export var click_target_path: String -export var scroll_target_path: String -export var scroll_bar_get_method: String -export var scroll_velocity_decaying_factor: float = 2.5 - -var pointer: Dictionary -var anchor: float -var offset: float -var screen_dpcm: float - -var click_target: Control -var scroll_target: Control -var target_scroll_bar: VScrollBar - - - -# @DAM -# Change this into an event-driven approach by changing the touch_scroll into a signal emitter and -# the target scrollable control a signal consumer. This way, the signal consumer will behave as the -# central node and will have the capability to detect when different touch_scroll sensors are -# activated and stop previous inputs (if required). -# Signal emitters should signal: -# - when an input drag starts -# - when an input drag stops -# - when an input drags -# - when an input clicks - - -func _ready(): - click_target = get_node(click_target_path) - scroll_target = get_node(scroll_target_path) - target_scroll_bar = scroll_target.call(scroll_bar_get_method) - pointer = { - index = -1, - initial_position = Vector2.ZERO, - current_position = Vector2.ZERO, - relative_position = Vector2.ZERO, - velocity = Vector2.ZERO, - was_dragged = false, - is_active = false, - } - screen_dpcm = float(OS.get_screen_dpi()) / 2.54 - - -func _process(delta: float): - # @DAM This only works for the stage... for the database this hides the touch_scroll after the first click. - if click_target.has_focus(): - mouse_filter = Control.MOUSE_FILTER_IGNORE - else: - mouse_filter = Control.MOUSE_FILTER_STOP - - if pointer.is_active: - var dragged_distance: float = - (pointer.current_position.y - pointer.initial_position.y) - offset = anchor + dragged_distance -# self.target_scroll_bar.value = offset - self.target_scroll_bar.value -= pointer.relative_position.y - pointer.relative_position = Vector2.ZERO - elif pointer.velocity.length() > 0.5: - pointer.velocity *= clamp((1.0 - scroll_velocity_decaying_factor * delta), 0.0, 1.0) - offset -= pointer.velocity.y * delta -# self.target_scroll_bar.value = offset - self.target_scroll_bar.value -= pointer.velocity.y * delta - - -func _gui_input(event: InputEvent): - - # @DAM A bug on GODOT-3.X makes events from non-mouse-emulated pointers (index > 0) unreliable. - if event is InputEventScreenTouch || event is InputEventScreenDrag: - if event.index != 0: - return - - if event is InputEventScreenTouch && (pointer.is_active == false || pointer.index == event.index): - var touch := event as InputEventScreenTouch - pointer.is_active = event.pressed - pointer.current_position = get_global_mouse_position() # touch.position - if pointer.is_active: - pointer.index = touch.index - pointer.initial_position = get_global_mouse_position() # touch.position - anchor = self.target_scroll_bar.value - else: - if pointer.was_dragged == false: # Click detected. - simulate_gui_click() - pointer.index = -1 - pointer.was_dragged = false - - if event is InputEventScreenDrag && event.index == pointer.index: - var drag := event as InputEventScreenDrag - pointer.current_position = get_global_mouse_position() # drag.position - pointer.velocity = drag.speed - pointer.relative_position = drag.relative - if pointer.current_position.distance_to(pointer.initial_position) / screen_dpcm > DRAG_THRESHOLD_CM: - pointer.was_dragged = true - - -func simulate_gui_click(): - - var position := self.get_global_mouse_position() - click_target.rect_global_position - - var event_touch := InputEventScreenTouch.new() - event_touch.index = 0 - event_touch.position = position - - var event_mouse := InputEventMouseButton.new() - event_mouse.button_index = BUTTON_LEFT - event_mouse.button_mask = BUTTON_MASK_LEFT - event_mouse.position = position - - event_mouse.pressed = true - event_touch.pressed = true - click_target._gui_input(event_mouse) - click_target._gui_input(event_touch) - - click_target.grab_focus() - - event_mouse.pressed = false - event_touch.pressed = false - click_target._gui_input(event_mouse) - click_target._gui_input(event_touch) - - -- cgit v1.2.3 From 88f355c29987c39b01727a08d3dfef8a312bd9ec Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 2 Jan 2022 03:04:48 +0000 Subject: Implement scroll by dragging on database and stage screens. --- logic/database.gd | 144 +++++++++++++++++----------------------------- logic/stage.gd | 150 +++++++++++++++++++++--------------------------- main.tscn | 61 ++++++++++---------- pointer_input_sensor.gd | 38 ++++++++---- readme.md | 18 +++++- 5 files changed, 192 insertions(+), 219 deletions(-) (limited to 'logic/stage.gd') diff --git a/logic/database.gd b/logic/database.gd index 41ba679..c492321 100644 --- a/logic/database.gd +++ b/logic/database.gd @@ -22,16 +22,19 @@ const ENTRY_PROTOTYPE: Dictionary = { "is_urgency": false, "notes": "", } +const POINTER_VELOCITY_DECAYING_FACTOR: float = 2.5 var db: Array var selected_idx: int var staged_idx: int +var is_pointer_dragging := false +var pointer_drag_velocity := 0.0 onready var stage: Stage = get_node("/root/main/stage") onready var delete_button: Button = get_node("actions/delete") onready var edit_button: Button = get_node("actions/edit") onready var add_button: Button = get_node("actions/add") -onready var v_scroll_bar: ScrollBar = get_v_scroll() # @DAM Stop scroll inertia when buttons are pressed. +onready var v_scroll_bar: ScrollBar = get_v_scroll() onready var drag_sensor: PointerInputSensor = get_node("drag_sensor") @@ -53,66 +56,25 @@ func _ready(): stage.connect("save", self, "save") stage.connect("discard", self, "discard") - drag_sensor.connect("on_press", self, "on_press") - drag_sensor.connect("on_drag", self, "on_drag") - drag_sensor.connect("on_end_drag", self, "on_end_drag") - drag_sensor.connect("on_click", self, "on_click") + drag_sensor.connect("on_press", self, "pointer_input_handler") + drag_sensor.connect("on_drag", self, "pointer_input_handler") + drag_sensor.connect("on_end_drag", self, "pointer_input_handler") + drag_sensor.connect("on_click", self, "pointer_input_handler") for it in db: self.add_item(get_entry_view(it)) -# @DAM Cleanup this code. -export var scroll_velocity_decaying_factor: float = 2.5 -var drag_velocity := 0.0 -var is_dragging := false - func _process(delta: float): - if is_dragging == false && abs(drag_velocity) > 0.5: - drag_velocity *= clamp((1.0 - scroll_velocity_decaying_factor * delta), 0.0, 1.0) - v_scroll_bar.value -= drag_velocity * delta - -func on_click(pointer: PointerInputSensor.PointerInputData): - var target := self - var position := target.get_global_mouse_position() - target.rect_global_position - - var event_touch := InputEventScreenTouch.new() - event_touch.index = 0 - event_touch.position = position - - var event_mouse := InputEventMouseButton.new() - event_mouse.button_index = BUTTON_LEFT - event_mouse.button_mask = BUTTON_MASK_LEFT - event_mouse.position = position - - event_mouse.pressed = true - event_touch.pressed = true - target._gui_input(event_mouse) - target._gui_input(event_touch) - - target.grab_focus() - - event_mouse.pressed = false - event_touch.pressed = false - target._gui_input(event_mouse) - target._gui_input(event_touch) - -func on_press(pointer: PointerInputSensor.PointerInputData): - is_dragging = true - -func on_end_drag(pointer: PointerInputSensor.PointerInputData): - is_dragging = false - -func on_drag(pointer: PointerInputSensor.PointerInputData): - is_dragging = true - drag_velocity = pointer.velocity.y - v_scroll_bar.value -= pointer.relative_position.y - - + # Apply drag movement inertia. + if is_pointer_dragging == false && abs(pointer_drag_velocity) > 0.5: + pointer_drag_velocity *= clamp((1.0 - POINTER_VELOCITY_DECAYING_FACTOR * delta), 0.0, 1.0) + v_scroll_bar.value -= pointer_drag_velocity * delta func _notification(what: int): - if visible == false: + # @DAM Yet to be polished. + if visible == false || has_focus() == false: return if what == MainLoop.NOTIFICATION_WM_GO_BACK_REQUEST: @@ -127,11 +89,7 @@ func get_entry_view(database_entry: Dictionary) -> String: func item_selected(index: int): - if selected_idx >= 0: - set_item_text(selected_idx, get_entry_view(db[selected_idx])) selected_idx = index - set_item_text(selected_idx, "> " + get_entry_view(db[selected_idx]) + " <") - func clear_selection(): @@ -223,40 +181,6 @@ func load_database(file_path: String = DATABASE_FILE_PATH): db.append(entry) fake_database() - return - - db = [ - instance_entry({ - "process_id": "000001", - "surgery_id": "100000", - "place": "central", - "date": "2020-01-31", - }), - instance_entry({ - "process_id": "000002", - "surgery_id": "200000", - "place": "central", - "date": "2020-02-31", - }), - instance_entry({ - "process_id": "000003", - "surgery_id": "300000", - "place": "central", - "date": "2020-03-31", - }), - instance_entry({ - "process_id": "000004", - "surgery_id": "400000", - "place": "central", - "date": "2020-04-31", - }), - instance_entry({ - "process_id": "000012", - "surgery_id": "210000", - "place": "central", - "date": "2020-12-31", - }), - ] func store_database(file_path: String = DATABASE_FILE_PATH): @@ -321,3 +245,43 @@ func fake_database(): "date": "%04d-%02d-%02d" % [date_year, date_month, date_day] })) + +func pointer_input_handler(pointer: PointerInputSensor.PointerInputData): + match pointer.action: + PointerInputSensor.PointerInputAction.ON_PRESS: + is_pointer_dragging = true + + PointerInputSensor.PointerInputAction.ON_DRAG: + is_pointer_dragging = true + pointer_drag_velocity = pointer.velocity.y + v_scroll_bar.value -= pointer.relative_position.y + + PointerInputSensor.PointerInputAction.ON_END_DRAG: + is_pointer_dragging = false + + PointerInputSensor.PointerInputAction.ON_CLICK: + var target := self + var position := target.get_global_mouse_position() - target.rect_global_position + + var event_touch := InputEventScreenTouch.new() + event_touch.index = 0 + event_touch.position = position + + var event_mouse := InputEventMouseButton.new() + event_mouse.button_index = BUTTON_LEFT + event_mouse.button_mask = BUTTON_MASK_LEFT + event_mouse.position = position + + event_mouse.pressed = true + event_touch.pressed = true + target._gui_input(event_mouse) + target._gui_input(event_touch) + + target.grab_focus() + + event_mouse.pressed = false + event_touch.pressed = false + target._gui_input(event_mouse) + target._gui_input(event_touch) + + diff --git a/logic/stage.gd b/logic/stage.gd index 62e0a58..27962c6 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -4,21 +4,10 @@ class_name Stage signal save # (database_entry: Dictionary) signal discard # () -#onready var process_id: LineEdit = get_node("touch_scroll/controls/process_id") -#onready var surgery_id: LineEdit = get_node("touch_scroll/controls/surgery_id") -#onready var date: DatePicker = get_node("touch_scroll/controls/date_picker") -#onready var place: LineEdit = get_node("touch_scroll/controls/place") -#onready var anesthetic: LineEdit = get_node("touch_scroll/controls/anesthetic") -#onready var first_assistant: LineEdit = get_node("touch_scroll/controls/first_assistant") -#onready var type: LineEdit = get_node("touch_scroll/controls/type") -#onready var sub_type: LineEdit = get_node("touch_scroll/controls/sub_type") -#onready var sub_sub_type: LineEdit = get_node("touch_scroll/controls/sub_sub_type") -#onready var pathology: LineEdit = get_node("touch_scroll/controls/pathology") -#onready var intervention: LineEdit = get_node("touch_scroll/controls/intervention") -#onready var is_urgency: Button = get_node("touch_scroll/controls/is_urgency") -#onready var notes: LineEdit = get_node("touch_scroll/controls/notes") -#onready var save_button: Button = get_node("touch_scroll/controls/save") -#onready var discard_button: Button = get_node("touch_scroll/controls/discard") +const POINTER_VELOCITY_DECAYING_FACTOR: float = 2.5 + +var is_pointer_dragging := false +var pointer_drag_velocity := 0.0 onready var process_id: LineEdit = get_node("controls/process_id") onready var surgery_id: LineEdit = get_node("controls/surgery_id") @@ -35,94 +24,46 @@ onready var is_urgency: Button = get_node("controls/is_urgency") onready var notes: LineEdit = get_node("controls/notes") onready var save_button: Button = get_node("controls/save") onready var discard_button: Button = get_node("controls/discard") +onready var v_scroll_bar: VScrollBar = get_v_scrollbar() func _ready(): save_button.connect("pressed", self, "save_action") discard_button.connect("pressed", self, "discard_action") - for it in get_node("controls").get_children(): -# print("%s" % it.name) + it = it as Control match it.name: "date_picker", "save", "discard": - print("- %s" % it.name) - + pass + _: -# "first_assistant": - print("+ %s" % it.name) var drag_sensor = PointerInputSensor.new() + it.add_child(drag_sensor) + drag_sensor.name = "drag_sensor" - drag_sensor.mouse_default_cursor_shape = Control.CURSOR_IBEAM - (it as Control).add_child(drag_sensor) drag_sensor.anchor_right = 1.0 drag_sensor.anchor_bottom = 1.0 - drag_sensor.connect("on_press", self, "on_press") - drag_sensor.connect("on_drag", self, "on_drag") - drag_sensor.connect("on_end_drag", self, "on_end_drag") - drag_sensor.connect("on_click", self, "on_click") - drag_sensor.on_process = funcref(self, "bazinga") - - -# @DAM Cleanup this code. -onready var v_scroll_bar: VScrollBar = get_v_scrollbar() -export var scroll_velocity_decaying_factor: float = 2.5 -var drag_velocity := 0.0 -var is_dragging := false + drag_sensor.connect("on_press", self, "pointer_input_handler") + drag_sensor.connect("on_drag", self, "pointer_input_handler") + drag_sensor.connect("on_end_drag", self, "pointer_input_handler") + drag_sensor.connect("on_click", self, "pointer_input_handler") + + it.connect("focus_entered", drag_sensor, "set_mouse_filter", [Control.MOUSE_FILTER_IGNORE]) + it.connect("focus_entered", drag_sensor, "mouse_default_cursor_shape", [Control.CURSOR_IBEAM]) + it.connect("focus_exited", drag_sensor, "set_mouse_filter", [Control.MOUSE_FILTER_STOP]) + it.connect("focus_exited", drag_sensor, "mouse_default_cursor_shape", [Control.CURSOR_ARROW]) + + if it is LineEdit: + it.connect("focus_exited", it, "deselect") -func bazinga(delta: float, pointer: PointerInputSensor): - if (pointer.get_parent() as Control).has_focus(): - pointer.mouse_filter = Control.MOUSE_FILTER_IGNORE - else: - pointer.mouse_filter = Control.MOUSE_FILTER_STOP func _process(delta: float): - if is_dragging == false && abs(drag_velocity) > 0.5: - drag_velocity *= clamp((1.0 - scroll_velocity_decaying_factor * delta), 0.0, 1.0) - v_scroll_bar.value -= drag_velocity * delta - -func on_click(pointer: PointerInputSensor.PointerInputData): - var target: Control = pointer.target.get_parent() - var position := target.get_global_mouse_position() - target.rect_global_position - - var event_touch := InputEventScreenTouch.new() - event_touch.index = 0 - event_touch.position = position - - var event_mouse := InputEventMouseButton.new() - event_mouse.button_index = BUTTON_LEFT - event_mouse.button_mask = BUTTON_MASK_LEFT - event_mouse.position = position - - event_mouse.pressed = true - event_touch.pressed = true - target._gui_input(event_mouse) - target._gui_input(event_touch) - - target.grab_focus() - - event_mouse.pressed = false - event_touch.pressed = false - target._gui_input(event_mouse) - target._gui_input(event_touch) - -func on_press(pointer: PointerInputSensor.PointerInputData): - is_dragging = true - -func on_end_drag(pointer: PointerInputSensor.PointerInputData): - is_dragging = false - -func on_drag(pointer: PointerInputSensor.PointerInputData): - is_dragging = true - drag_velocity = pointer.velocity.y - v_scroll_bar.value -= pointer.relative_position.y - - - - - - + # Apply drag movement inertia. + if is_pointer_dragging == false && abs(pointer_drag_velocity) > 0.5: + pointer_drag_velocity *= clamp((1.0 - POINTER_VELOCITY_DECAYING_FACTOR * delta), 0.0, 1.0) + v_scroll_bar.value -= pointer_drag_velocity * delta func save_action(): @@ -179,3 +120,42 @@ func _notification(what: int): discard_action() +func pointer_input_handler(pointer: PointerInputSensor.PointerInputData): + match pointer.action: + PointerInputSensor.PointerInputAction.ON_PRESS: + is_pointer_dragging = true + + PointerInputSensor.PointerInputAction.ON_DRAG: + is_pointer_dragging = true + pointer_drag_velocity = pointer.velocity.y + v_scroll_bar.value -= pointer.relative_position.y + + PointerInputSensor.PointerInputAction.ON_END_DRAG: + is_pointer_dragging = false + + PointerInputSensor.PointerInputAction.ON_CLICK: + var target: Control = pointer.target.get_parent() + var position := target.get_global_mouse_position() - target.rect_global_position + + var event_touch := InputEventScreenTouch.new() + event_touch.index = 0 + event_touch.position = position + + var event_mouse := InputEventMouseButton.new() + event_mouse.button_index = BUTTON_LEFT + event_mouse.button_mask = BUTTON_MASK_LEFT + event_mouse.position = position + + event_mouse.pressed = true + event_touch.pressed = true + target._gui_input(event_mouse) + target._gui_input(event_touch) + + target.grab_focus() + + event_mouse.pressed = false + event_touch.pressed = false + target._gui_input(event_mouse) + target._gui_input(event_touch) + + diff --git a/main.tscn b/main.tscn index 331d94d..b91cdc0 100644 --- a/main.tscn +++ b/main.tscn @@ -24,6 +24,7 @@ __meta__ = { anchor_right = 1.0 anchor_bottom = 1.0 margin_top = 60.0 +mouse_filter = 2 script = ExtResource( 5 ) __meta__ = { "_edit_use_anchors_": false @@ -103,15 +104,15 @@ size_flags_vertical = 3 [node name="process_id" type="LineEdit" parent="stage/controls"] margin_right = 1080.0 -margin_bottom = 52.0 +margin_bottom = 49.0 placeholder_text = "Nº Processo" caret_blink = true caret_blink_speed = 0.5 [node name="surgery_id" type="LineEdit" parent="stage/controls"] -margin_top = 56.0 +margin_top = 53.0 margin_right = 1080.0 -margin_bottom = 108.0 +margin_bottom = 102.0 placeholder_text = "Nº Cirurgia" caret_blink = true caret_blink_speed = 0.5 @@ -119,93 +120,93 @@ caret_blink_speed = 0.5 [node name="date_picker" parent="stage/controls" instance=ExtResource( 2 )] anchor_right = 0.0 anchor_bottom = 0.0 -margin_top = 112.0 +margin_top = 106.0 margin_right = 1080.0 -margin_bottom = 312.0 +margin_bottom = 306.0 rect_min_size = Vector2( 400, 200 ) [node name="place" type="LineEdit" parent="stage/controls"] -margin_top = 316.0 +margin_top = 310.0 margin_right = 1080.0 -margin_bottom = 368.0 +margin_bottom = 359.0 placeholder_text = "Local" caret_blink = true caret_blink_speed = 0.5 [node name="anesthetic" type="LineEdit" parent="stage/controls"] -margin_top = 372.0 +margin_top = 363.0 margin_right = 1080.0 -margin_bottom = 424.0 -placeholder_text = "Anesthesics" +margin_bottom = 412.0 +placeholder_text = "Anestesia" caret_blink = true caret_blink_speed = 0.5 [node name="first_assistant" type="LineEdit" parent="stage/controls"] -margin_top = 428.0 +margin_top = 416.0 margin_right = 1080.0 -margin_bottom = 480.0 +margin_bottom = 465.0 placeholder_text = "1º Ajudante" caret_blink = true caret_blink_speed = 0.5 [node name="type" type="LineEdit" parent="stage/controls"] -margin_top = 484.0 +margin_top = 469.0 margin_right = 1080.0 -margin_bottom = 536.0 +margin_bottom = 518.0 placeholder_text = "Tipo" caret_blink = true caret_blink_speed = 0.5 [node name="sub_type" type="LineEdit" parent="stage/controls"] -margin_top = 540.0 +margin_top = 522.0 margin_right = 1080.0 -margin_bottom = 592.0 +margin_bottom = 571.0 placeholder_text = "Subtipo" caret_blink = true caret_blink_speed = 0.5 [node name="sub_sub_type" type="LineEdit" parent="stage/controls"] -margin_top = 596.0 +margin_top = 575.0 margin_right = 1080.0 -margin_bottom = 648.0 +margin_bottom = 624.0 placeholder_text = "Sub-Subtipo" caret_blink = true caret_blink_speed = 0.5 [node name="pathology" type="LineEdit" parent="stage/controls"] -margin_top = 652.0 +margin_top = 628.0 margin_right = 1080.0 -margin_bottom = 704.0 +margin_bottom = 677.0 placeholder_text = "Patologia" caret_blink = true caret_blink_speed = 0.5 [node name="intervention" type="LineEdit" parent="stage/controls"] -margin_top = 708.0 +margin_top = 681.0 margin_right = 1080.0 -margin_bottom = 760.0 +margin_bottom = 730.0 placeholder_text = "Intervenção" caret_blink = true caret_blink_speed = 0.5 [node name="is_urgency" type="CheckBox" parent="stage/controls"] -margin_top = 764.0 +margin_top = 734.0 margin_right = 1080.0 -margin_bottom = 814.0 +margin_bottom = 781.0 text = "Urgência" [node name="notes" type="LineEdit" parent="stage/controls"] -margin_top = 818.0 +margin_top = 785.0 margin_right = 1080.0 -margin_bottom = 870.0 +margin_bottom = 834.0 placeholder_text = "Notas" caret_blink = true caret_blink_speed = 0.5 [node name="discard" type="Button" parent="stage/controls"] -margin_top = 928.0 +margin_top = 838.0 margin_right = 1080.0 -margin_bottom = 978.0 +margin_bottom = 888.0 grow_horizontal = 2 grow_vertical = 2 rect_min_size = Vector2( 0, 50 ) @@ -215,9 +216,9 @@ __meta__ = { } [node name="save" type="Button" parent="stage/controls"] -margin_top = 874.0 +margin_top = 892.0 margin_right = 1080.0 -margin_bottom = 924.0 +margin_bottom = 942.0 grow_horizontal = 2 grow_vertical = 2 rect_min_size = Vector2( 0, 50 ) diff --git a/pointer_input_sensor.gd b/pointer_input_sensor.gd index 95e1b4b..27f257c 100644 --- a/pointer_input_sensor.gd +++ b/pointer_input_sensor.gd @@ -1,8 +1,7 @@ extends Control class_name PointerInputSensor -export var drag_threshold_cm: float = 0.250 - +# All on_ACTION signals have a single argument of type PointerInputData. signal on_press signal on_release #signal on_release_outside @@ -14,19 +13,35 @@ signal on_begin_drag signal on_drag signal on_end_drag +enum PointerInputAction { + UNDEFINED, + ON_PRESS, + ON_RELEASE, +# ON_RELEASE_OUTSIDE, + ON_CLICK, + ON_ENTER, + ON_EXIT, +# ON_EXIT_APP_WINDOW, + ON_BEGIN_DRAG, + ON_DRAG, + ON_END_DRAG, +} + class PointerInputData: var target: PointerInputSensor var index := -1 var initial_position := Vector2.ZERO var current_position := Vector2.ZERO - var relative_position = Vector2.ZERO + var relative_position := Vector2.ZERO var velocity := Vector2.ZERO var was_dragged := false var is_pressed := false + var action: int = PointerInputAction.UNDEFINED + +export var drag_threshold_cm: float = 0.250 var pointer: PointerInputData var screen_dpcm: float -var on_process: FuncRef func _ready(): @@ -37,12 +52,8 @@ func _ready(): connect("mouse_entered", self, "_on_enter_exit", [false]) -func _process(delta: float): - if on_process != null && on_process.is_valid(): - on_process.call_func(delta, self) - - func _on_enter_exit(is_inside: bool): + pointer.action = PointerInputAction.ON_ENTER if is_inside else PointerInputAction.ON_EXIT emit_signal("on_enter" if is_inside else "on_exit", pointer) @@ -60,12 +71,16 @@ func _gui_input(event: InputEvent): if pointer.is_pressed: pointer.index = touch.index pointer.initial_position = get_global_mouse_position() + pointer.action = PointerInputAction.ON_PRESS emit_signal("on_press", pointer) else: + pointer.action = PointerInputAction.ON_RELEASE emit_signal("on_release", pointer) if pointer.was_dragged == false: + pointer.action = PointerInputAction.ON_CLICK emit_signal("on_click", pointer) else: + pointer.action = PointerInputAction.ON_END_DRAG emit_signal("on_end_drag", pointer) pointer.index = -1 pointer.was_dragged = false @@ -75,10 +90,11 @@ func _gui_input(event: InputEvent): pointer.current_position = get_global_mouse_position() pointer.velocity = drag.speed pointer.relative_position = drag.relative - # @DAM Instead of constantly converting the pointer distance to cm... just conver the drag_threshold_cm to pixels - if pointer.was_dragged == false && pointer.current_position.distance_to(pointer.initial_position) / screen_dpcm > drag_threshold_cm: + if pointer.was_dragged == false && pointer.current_position.distance_to(pointer.initial_position) > drag_threshold_cm * screen_dpcm: pointer.was_dragged = true + pointer.action = PointerInputAction.ON_BEGIN_DRAG emit_signal("on_begin_drag", pointer) + pointer.action = PointerInputAction.ON_DRAG emit_signal("on_drag", pointer) diff --git a/readme.md b/readme.md index c2d73b0..4a28a70 100644 --- a/readme.md +++ b/readme.md @@ -10,15 +10,27 @@ Surgery Log - [x] Solve how entries are shared across db_screen and stage_screen; - [x] Remove db_entry; - [x] Rename db script/node to database; -- [ ] Input form fields is a column of input items; each input item has (from left to right) a 'LineEdit' and a 'Button'; pressing the button shows a list with possible entries for that field, and selecting an entry will coyp that entry text to the 'LineEdit'; if the 'LineEdit' has text, it's 'Clear Button Enabled' is set and the 'Button' should become smaller (to allow more text to be visible. +- [x] scrolling down on the database screen jumps to the end of the list immediatelly; +- [ ] load/store database CSV file; +- [ ] load/store filters CSV file; +- [ ] add auto-fill button buttons on stage screen: + - should show a pop-up with multiple options filtered according to current filters; + - allow options to be scrolled by dragging; + - selecting option puts that text on the associated LineEdit; +- [ ] on stage screen: if a LineEdit is has text, it's "Clear Button Enabled" is set and the auto-entry-button should become smaller to allow more text to be visible; +- [ ] fix back button: + - on stage screen should show pop-up asking it changes are to be discarded; + - on file-pickers screen should close them; + - on about screen should close it; + - on auto-fill pop-up, should close it; + - on database screen, should deselect selected item, otherwise should quit the app; +- [ ] add pop-up asking if changes are to be discarded once the stage screen's discard button is pressed; - [ ] Share DB: - share db via html email with db inserted in encoded downliadable field; - Use this to send email: https://docs.godotengine.org/en/stable/classes/class_os.html#class-os-method-shell-open -- [ ] File dialogs should be dismissed when back button is pressed (instead of closing the app); - [ ] Translations: - https://docs.godotengine.org/en/stable/getting_started/workflow/assets/importing_translations.html - https://docs.godotengine.org/en/stable/tutorials/i18n/internationalizing_games.html#introduction -- [ ] Set tabs titles dynamically: https://docs.godotengine.org/en/stable/classes/class_tabcontainer.html?highlight=tabcontainer#class-tabcontainer-method-set-tab-title - [ ] Create two themes: - [ ] theme_light - [ ] theme_dark -- cgit v1.2.3 From 06b2f34ba64726028f494060376044531d763668 Mon Sep 17 00:00:00 2001 From: dam Date: Tue, 4 Jan 2022 22:34:48 +0000 Subject: Implement clear and export data actions. Merge and reuse modal dialogs. --- logic/database.gd | 19 +++++--- logic/stage.gd | 6 +-- main.gd | 6 +-- main.tscn | 44 +++---------------- menu/menu.gd | 126 ++++++++++++++++++++++++++---------------------------- readme.md | 17 +++++++- 6 files changed, 102 insertions(+), 116 deletions(-) (limited to 'logic/stage.gd') diff --git a/logic/database.gd b/logic/database.gd index 438a2dd..2285470 100644 --- a/logic/database.gd +++ b/logic/database.gd @@ -12,7 +12,7 @@ const ENTRY_PROTOTYPE: Dictionary = { "date_year": 0, "date_month": 0, "date_day": 0, - "anesthetic": "", + "anesthesia": "", "first_assistant": "", "type": "", "sub_type": "", @@ -195,6 +195,14 @@ func store_database(file_path: String = DATABASE_FILE_PATH): file.close() +func clear_database(save_changes: bool = false): + clear_selection() + self.clear() + db.resize(0) + if save_changes: + store_database() + + static func instance_entry(params: Dictionary = {}) -> Dictionary: var new_entry := ENTRY_PROTOTYPE.duplicate(true) new_entry.process_id = params.get("process_id", "") @@ -207,7 +215,7 @@ static func instance_entry(params: Dictionary = {}) -> Dictionary: new_entry.date_day = params.get("date_day", today.day) new_entry.date = params.get("date", get_entry_date(new_entry)) # @DAM We should store only one version of the date. - new_entry.anesthetic = params.get("anesthetic", "") + new_entry.anesthesia = params.get("anesthesia", "") new_entry.first_assistant = params.get("first_assistant", "") new_entry.type = params.get("type", "") new_entry.sub_type = params.get("sub_type", "") @@ -234,9 +242,8 @@ static func set_entry_date(entry: Dictionary, date: String): entry.date_day = int(date.substr(month_day_idx + 1)) -func fake_database(): - self.clear() - db.resize(0) +func fake_database(save_changes: bool = false): + clear_database() for idx in range(500): var date_year = 1 + int(float(idx) / 365.0) var date_month = idx % 12 @@ -248,6 +255,8 @@ func fake_database(): }) db.append(fake_entry) self.add_item(get_entry_view(fake_entry)) + if save_changes: + store_database() func pointer_input_handler(pointer: PointerInputSensor.PointerInputData): diff --git a/logic/stage.gd b/logic/stage.gd index 27962c6..73d905e 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -13,7 +13,7 @@ onready var process_id: LineEdit = get_node("controls/process_id") onready var surgery_id: LineEdit = get_node("controls/surgery_id") onready var date: DatePicker = get_node("controls/date_picker") onready var place: LineEdit = get_node("controls/place") -onready var anesthetic: LineEdit = get_node("controls/anesthetic") +onready var anesthesia: LineEdit = get_node("controls/anesthesia") onready var first_assistant: LineEdit = get_node("controls/first_assistant") onready var type: LineEdit = get_node("controls/type") onready var sub_type: LineEdit = get_node("controls/sub_type") @@ -81,7 +81,7 @@ func set_stage(entry: Dictionary): surgery_id.text = entry.surgery_id date.set_date(entry.date_year, entry.date_month, entry.date_day) place.text = entry.place - anesthetic.text = entry.anesthetic + anesthesia.text = entry.anesthesia first_assistant.text = entry.first_assistant type.text = entry.type sub_type.text = entry.sub_type @@ -102,7 +102,7 @@ func get_stage() -> Dictionary: "date_month": date.get_month(), "date_day": date.get_day(), "place": place.text, - "anesthetic": anesthetic.text, + "anesthesia": anesthesia.text, "first_assistant": first_assistant.text, "type": type.text, "sub_type": sub_type.text, diff --git a/main.gd b/main.gd index d242bda..c5f7d0d 100644 --- a/main.gd +++ b/main.gd @@ -4,10 +4,8 @@ var timeout: float onready var controls_sensible_to_keyboard: Array = [ self, - get_node("/root/main/about"), - get_node("/root/main/delete_filters"), - get_node("/root/main/import_filters"), - get_node("/root/main/export_filters"), + get_node("/root/main/file_picker"), + get_node("/root/main/confirm_action"), ] diff --git a/main.tscn b/main.tscn index b91cdc0..d4ff096 100644 --- a/main.tscn +++ b/main.tscn @@ -133,7 +133,7 @@ placeholder_text = "Local" caret_blink = true caret_blink_speed = 0.5 -[node name="anesthetic" type="LineEdit" parent="stage/controls"] +[node name="anesthesia" type="LineEdit" parent="stage/controls"] margin_top = 363.0 margin_right = 1080.0 margin_bottom = 412.0 @@ -237,36 +237,12 @@ __meta__ = { "_edit_use_anchors_": false } -[node name="about" type="AcceptDialog" parent="."] +[node name="file_picker" type="FileDialog" parent="."] anchor_right = 1.0 anchor_bottom = 1.0 size_flags_horizontal = 3 size_flags_vertical = 3 -window_title = "" -dialog_autowrap = true -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="delete_filters" type="ConfirmationDialog" parent="."] -anchor_right = 1.0 -anchor_bottom = 1.0 -size_flags_horizontal = 3 -size_flags_vertical = 3 -window_title = "delete filters" -dialog_text = "Are you sure you want to delete all filters?" -dialog_autowrap = true -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="import_filters" type="FileDialog" parent="."] -anchor_right = 1.0 -anchor_bottom = 1.0 -size_flags_horizontal = 3 -size_flags_vertical = 3 -window_title = "Open a File" -mode = 0 +window_title = "PICK FILE" access = 2 filters = PoolStringArray( "*.csv" ) show_hidden_files = true @@ -276,20 +252,14 @@ __meta__ = { "_edit_use_anchors_": false } -[node name="export_filters" type="FileDialog" parent="."] +[node name="confirm_action" type="ConfirmationDialog" parent="."] anchor_right = 1.0 anchor_bottom = 1.0 size_flags_horizontal = 3 size_flags_vertical = 3 -window_title = "export filters" -access = 2 -filters = PoolStringArray( "*.csv" ) -show_hidden_files = true -current_dir = "" -current_path = "" -__meta__ = { -"_edit_use_anchors_": false -} +window_title = "CONFIRM ACTION" +dialog_text = "Are you sure you want to delete all filters?" +dialog_autowrap = true [node name="debug" type="Label" parent="."] visible = false diff --git a/menu/menu.gd b/menu/menu.gd index 36beee8..3384436 100644 --- a/menu/menu.gd +++ b/menu/menu.gd @@ -4,100 +4,94 @@ const menu_items: Array = [ { label = "IMPORT FILTERS", action = "_menu_import_filters_action" }, { label = "EXPORT FILTERS", action = "_menu_export_filters_action" }, { label = "CLEAR FILTERS", action = "_menu_clear_filters_action" }, + { label = "EXPORT DATA", action = "_menu_export_data_action" }, + { label = "CLEAR DATA", action = "_menu_clear_data_action" }, { label = "ABOUT", action = "_menu_about_action" }, { label = "FAKE_DB", action = "_menu_fake_db_action" }, ] const license_font_b612: String = "res://licenses/font_b612.txt" const license_godot: String = "res://licenses/godot.txt" -onready var popup: PopupMenu = get_popup() -onready var debug: Label = get_node("/root/main/debug") as Label +onready var popup := get_popup() as PopupMenu +onready var confirm_action := get_node("/root/main/confirm_action") as ConfirmationDialog +onready var file_picker := get_node("/root/main/file_picker") as FileDialog +onready var database := get_node("/root/main/database") as Database + func _ready(): - for idx in range(menu_items.size()): popup.add_item(menu_items[idx].label, idx) popup.connect("id_pressed", self, "id_pressed") - -# load_file(file) - + file_picker.get_cancel().connect("pressed", self, "dialog_cancelled", ["file_selected"]) + confirm_action.get_cancel().connect("pressed", self, "dialog_cancelled", ["confirmed"]) func id_pressed(id: int): - debug.text += "'%d':'%s'" % [id, menu_items[id].action] self.call_deferred(menu_items[id].action) - debug.text += "!\n" -var csv_file: Array -var has_permissions := false +func dialog_cancelled(confirmation_signal_name: String): + var confirmation_handlers = confirm_action.get_signal_connection_list(confirmation_signal_name) + for it in confirmation_handlers: + confirm_action.disconnect(it.signal, it.target, it.method) + + func _menu_import_filters_action(): - -# printerr("pressed: %s" % get_stack()[0]); - debug.text += "> import: " - - if OS.get_name() == "Android": - while not has_permissions: - var permissions := Array(OS.get_granted_permissions()) - if not permissions.has("android.permission.READ_EXTERNAL_STORAGE") \ - or not permissions.has("android.permission.WRITE_EXTERNAL_STORAGE"): - OS.request_permissions() - # await get_tree().create_timer(1).timeout - yield(get_tree().create_timer(1), "timeout") # - for Godot 3 branch - else: - has_permissions = true - - - - var file_dialog := get_node("/root/main/import_filters") as FileDialog - file_dialog.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS) - file_dialog.connect("file_selected", self, "file_selected", [], CONNECT_ONESHOT) - file_dialog.show_modal(true) - file_dialog.invalidate() -# printerr("download: '%s'" % OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS)) - -func file_selected(path: String): - debug.text += "'%s'\n" % path - - var file = File.new() - file.open(path, File.READ) - while file.eof_reached() == false: - var line = file.get_line() - csv_file.append(line) - printerr("%s" % line) - file.close() + return + file_picker.window_title = "IMPORT FILTERS" + file_picker.mode = FileDialog.MODE_OPEN_FILE + file_picker.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS) +# file_picker.connect("file_selected", filters, "TODO", [], CONNECT_ONESHOT) +# file_picker.show_modal(true) +# file_picker.invalidate() + func _menu_export_filters_action(): - debug.text += "> export: \n" -# printerr("pressed: %s" % get_stack()[0]); - var file_dialog := get_node("/root/main/export_filters") as FileDialog - file_dialog.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS) - file_dialog.connect("file_selected", self, "file_selected_export", [], CONNECT_ONESHOT) - file_dialog.show_modal(true) - file_dialog.invalidate() - -func file_selected_export(path: String): - debug.text += "'%s'\n" % path - var file = File.new() - file.open(path, File.WRITE) - for ln in csv_file: - file.store_line(ln) - file.close() + return + file_picker.window_title = "EXPORT FILTERS" + file_picker.mode = FileDialog.MODE_SAVE_FILE + file_picker.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS) +# file_picker.connect("file_selected", filters, "TODO", [], CONNECT_ONESHOT) +# file_picker.show_modal(true) +# file_picker.invalidate() func _menu_clear_filters_action(): - debug.text += "> clear\n" -# printerr("pressed: %s" % get_stack()[0]); - (get_node("/root/main/delete_filters") as ConfirmationDialog).show_modal(true) + return + confirm_action.window_title = "CLEAR FILTERS" + confirm_action.dialog_text = "Do you want to delete all filters?" +# confirm_action.connect("confirmed", filters, "TDO", [true], CONNECT_ONESHOT) +# confirm_action.show_modal(true) + + +func _menu_export_data_action(): + file_picker.window_title = "EXPORT DATA" + file_picker.mode = FileDialog.MODE_SAVE_FILE + file_picker.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS) + file_picker.connect("file_selected", database, "store_database", [], CONNECT_ONESHOT) + file_picker.show_modal(true) + file_picker.invalidate() + + +func _menu_clear_data_action(): + confirm_action.window_title = "CLEAR DATA" + confirm_action.dialog_text = "Do you want to delete all entries from the database?" + confirm_action.connect("confirmed", database, "clear_database", [true], CONNECT_ONESHOT) + confirm_action.show_modal(true) func _menu_about_action(): -# printerr("pressed: %s" % get_stack()[0]); - debug.text += "> about\n" - (get_node("/root/main/about") as AcceptDialog).show_modal() + confirm_action.window_title = "FAKE DB" + confirm_action.dialog_text = "About text here!" +# confirm_action.connect("confirmed", database, "fake_database", [true], CONNECT_ONESHOT) + confirm_action.show_modal(true) + func _menu_fake_db_action(): - get_node("/root/main/database").fake_database() + confirm_action.window_title = "FAKE DB" + confirm_action.dialog_text = "Do you want to delete all entries from the database and replace by fake entries?" + confirm_action.connect("confirmed", database, "fake_database", [true], CONNECT_ONESHOT) + confirm_action.show_modal(true) diff --git a/readme.md b/readme.md index 026c5eb..fc33cf1 100644 --- a/readme.md +++ b/readme.md @@ -14,7 +14,22 @@ Surgery Log - [x] edited entry does not show updated once saved; - [x] edited entry shows updated when selected then press back button; - [x] load/store database CSV file; -- [ ] export database to CSV file; +- [x] export database to CSV file; +- [x] allow to reset database; +- [ ] Implement file access permission check on Android: + ```py + if OS.get_name() == "Android": + var has_permissions := false + while not has_permissions: + var permissions := Array(OS.get_granted_permissions()) + if not permissions.has("android.permission.READ_EXTERNAL_STORAGE") \ + or not permissions.has("android.permission.WRITE_EXTERNAL_STORAGE"): + OS.request_permissions() + # await get_tree().create_timer(1).timeout + yield(get_tree().create_timer(1), "timeout") # - for Godot 3 branch + else: + has_permissions = true + ``` - [ ] load/store filters CSV file; - [ ] import/export filters to CSV file; - [ ] add auto-fill buttons on stage screen: -- cgit v1.2.3 From 6a2887783f19e0a94db00dea014e1065b87e626c Mon Sep 17 00:00:00 2001 From: dam Date: Thu, 6 Jan 2022 11:19:43 +0000 Subject: Prepare filters' logic. --- date_picker/date_picker.gd | 6 ++-- date_picker/value_picker.gd | 8 ++--- logic/database.gd | 30 +++++++++---------- logic/stage.gd | 72 ++++++++++++++++++++++++++++++++++----------- menu/menu.gd | 23 +++++++-------- readme.md | 4 +-- 6 files changed, 89 insertions(+), 54 deletions(-) (limited to 'logic/stage.gd') diff --git a/date_picker/date_picker.gd b/date_picker/date_picker.gd index fb9cca2..e2a793f 100644 --- a/date_picker/date_picker.gd +++ b/date_picker/date_picker.gd @@ -16,9 +16,9 @@ const days_per_month: Dictionary = { 12: 31, } -onready var year_picker: ValuePicker = get_node("year") -onready var month_picker: ValuePicker = get_node("month") -onready var day_picker: ValuePicker = get_node("day") +onready var year_picker := get_node("year") as ValuePicker +onready var month_picker := get_node("month") as ValuePicker +onready var day_picker := get_node("day") as ValuePicker func _process(delta: float): diff --git a/date_picker/value_picker.gd b/date_picker/value_picker.gd index fcd7dff..7429edd 100644 --- a/date_picker/value_picker.gd +++ b/date_picker/value_picker.gd @@ -10,10 +10,10 @@ const DRAG_THRESHOLD_CM: float = 0.250 export var min_value: int export var max_value: int -onready var input: LineEdit = get_node("current/input") -onready var label_previous: Label = get_node("previous") -onready var label_current: Label = get_node("current") -onready var label_next: Label = get_node("next") +onready var input := get_node("current/input") as LineEdit +onready var label_previous := get_node("previous") as Label +onready var label_current := get_node("current") as Label +onready var label_next := get_node("next") as Label var pointer: Dictionary var anchor: float diff --git a/logic/database.gd b/logic/database.gd index 2285470..47cddbc 100644 --- a/logic/database.gd +++ b/logic/database.gd @@ -30,12 +30,12 @@ var staged_idx: int var is_pointer_dragging := false var pointer_drag_velocity := 0.0 -onready var stage: Stage = get_node("/root/main/stage") -onready var delete_button: Button = get_node("actions/delete") -onready var edit_button: Button = get_node("actions/edit") -onready var add_button: Button = get_node("actions/add") -onready var v_scroll_bar: ScrollBar = get_v_scroll() -onready var drag_sensor: PointerInputSensor = get_node("drag_sensor") +onready var stage := get_node("/root/main/stage") #as Stage # @DAM Solve cyclic load. +onready var delete_button := get_node("actions/delete") as Button +onready var edit_button := get_node("actions/edit") as Button +onready var add_button := get_node("actions/add") as Button +onready var v_scroll_bar := get_v_scroll() as ScrollBar +onready var drag_sensor := get_node("drag_sensor") as PointerInputSensor func _init(): @@ -53,8 +53,8 @@ func _ready(): edit_button.connect("pressed", self, "edit_action") add_button.connect("pressed", self, "add_action") - stage.connect("save", self, "save") - stage.connect("discard", self, "discard") + stage.connect("save", self, "save_stage") + stage.connect("discard", self, "discard_stage") drag_sensor.connect("on_press", self, "pointer_input_handler") drag_sensor.connect("on_drag", self, "pointer_input_handler") @@ -128,15 +128,15 @@ func scroll_down(): v_scroll_bar.value = v_scroll_bar.max_value -func save(database_entry: Dictionary): - database_entry = instance_entry(database_entry) # @DAM Maybe we could not be creating endless dictionaries? +func save_stage(entry: Dictionary): + entry = instance_entry(entry) # @DAM Maybe we could not be creating endless dictionaries? var next_selected_idx: int if staged_idx >= 0: - db[staged_idx] = database_entry + db[staged_idx] = entry next_selected_idx = staged_idx else: - db.append(database_entry) - add_item(get_entry_view(database_entry)) + db.append(entry) + add_item(get_entry_view(entry)) call_deferred("scroll_down") next_selected_idx = db.size() - 1 @@ -152,7 +152,7 @@ func save(database_entry: Dictionary): grab_focus() -func discard(): +func discard_stage(): staged_idx = -1 self.visible = true grab_focus() @@ -163,7 +163,7 @@ func load_database(file_path: String = DATABASE_FILE_PATH): file.open(file_path, File.READ_WRITE) var headers: PoolStringArray var is_first_line := true -# while database_file.eof_reached() == false: # @DAM Why this? +# while file.eof_reached() == false: # @DAM Why this? while file.get_position() < file.get_len(): var csv_entry := file.get_csv_line() if is_first_line: diff --git a/logic/stage.gd b/logic/stage.gd index 73d905e..dd345e8 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -4,27 +4,29 @@ class_name Stage signal save # (database_entry: Dictionary) signal discard # () +const FILTERS_FILE_PATH: String = "user://filters.csv" const POINTER_VELOCITY_DECAYING_FACTOR: float = 2.5 var is_pointer_dragging := false var pointer_drag_velocity := 0.0 - -onready var process_id: LineEdit = get_node("controls/process_id") -onready var surgery_id: LineEdit = get_node("controls/surgery_id") -onready var date: DatePicker = get_node("controls/date_picker") -onready var place: LineEdit = get_node("controls/place") -onready var anesthesia: LineEdit = get_node("controls/anesthesia") -onready var first_assistant: LineEdit = get_node("controls/first_assistant") -onready var type: LineEdit = get_node("controls/type") -onready var sub_type: LineEdit = get_node("controls/sub_type") -onready var sub_sub_type: LineEdit = get_node("controls/sub_sub_type") -onready var pathology: LineEdit = get_node("controls/pathology") -onready var intervention: LineEdit = get_node("controls/intervention") -onready var is_urgency: Button = get_node("controls/is_urgency") -onready var notes: LineEdit = get_node("controls/notes") -onready var save_button: Button = get_node("controls/save") -onready var discard_button: Button = get_node("controls/discard") -onready var v_scroll_bar: VScrollBar = get_v_scrollbar() +var filters := {} + +onready var process_id := get_node("controls/process_id") as LineEdit +onready var surgery_id := get_node("controls/surgery_id") as LineEdit +onready var date := get_node("controls/date_picker") as DatePicker +onready var place := get_node("controls/place") as LineEdit +onready var anesthesia := get_node("controls/anesthesia") as LineEdit +onready var first_assistant := get_node("controls/first_assistant") as LineEdit +onready var type := get_node("controls/type") as LineEdit +onready var sub_type := get_node("controls/sub_type") as LineEdit +onready var sub_sub_type := get_node("controls/sub_sub_type") as LineEdit +onready var pathology := get_node("controls/pathology") as LineEdit +onready var intervention := get_node("controls/intervention") as LineEdit +onready var is_urgency := get_node("controls/is_urgency") as Button +onready var notes := get_node("controls/notes") as LineEdit +onready var save_button := get_node("controls/save") as Button +onready var discard_button := get_node("controls/discard") as Button +onready var v_scroll_bar := get_v_scrollbar() as VScrollBar func _ready(): @@ -159,3 +161,39 @@ func pointer_input_handler(pointer: PointerInputSensor.PointerInputData): target._gui_input(event_touch) +func load_filters(file_path: String = FILTERS_FILE_PATH): + var file := File.new() + file.open(file_path, File.READ_WRITE) + var headers: PoolStringArray + var is_first_line := true +# while file.eof_reached() == false: # @DAM Why this? + while file.get_position() < file.get_len(): + var csv_entry := file.get_csv_line() + if is_first_line: + is_first_line = false + headers = csv_entry + continue + var entry = Database.ENTRY_PROTOTYPE.duplicate(true) +# for idx in headers.size(): +# var field_name := headers[idx] +# var field_value := csv_entry[idx] +# match field_name: +# "date_year", "date_month", "date_day": +# entry[field_name] = int(field_value) +# "is_urgency": +# entry[field_name] = true if field_value.strip_edges().to_lower() == "true" else false +# _: +# entry[field_name] = field_value +# filters.append(entry) + + +func store_filters(file_path: String = FILTERS_FILE_PATH): + pass # @DAM TODO + + +func clear_filters(save_changes: bool = false): + pass # @DAM TODO + if save_changes: + store_filters() + + diff --git a/menu/menu.gd b/menu/menu.gd index 3384436..b5e1ef2 100644 --- a/menu/menu.gd +++ b/menu/menu.gd @@ -16,6 +16,7 @@ onready var popup := get_popup() as PopupMenu onready var confirm_action := get_node("/root/main/confirm_action") as ConfirmationDialog onready var file_picker := get_node("/root/main/file_picker") as FileDialog onready var database := get_node("/root/main/database") as Database +onready var stage := get_node("/root/main/stage") as Stage func _ready(): @@ -37,31 +38,28 @@ func dialog_cancelled(confirmation_signal_name: String): func _menu_import_filters_action(): - return file_picker.window_title = "IMPORT FILTERS" file_picker.mode = FileDialog.MODE_OPEN_FILE file_picker.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS) -# file_picker.connect("file_selected", filters, "TODO", [], CONNECT_ONESHOT) -# file_picker.show_modal(true) -# file_picker.invalidate() + file_picker.connect("file_selected", stage, "load_filters", [], CONNECT_ONESHOT) + file_picker.show_modal(true) + file_picker.invalidate() func _menu_export_filters_action(): - return file_picker.window_title = "EXPORT FILTERS" file_picker.mode = FileDialog.MODE_SAVE_FILE file_picker.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS) -# file_picker.connect("file_selected", filters, "TODO", [], CONNECT_ONESHOT) -# file_picker.show_modal(true) -# file_picker.invalidate() + file_picker.connect("file_selected", stage, "store_filters", [], CONNECT_ONESHOT) + file_picker.show_modal(true) + file_picker.invalidate() func _menu_clear_filters_action(): - return confirm_action.window_title = "CLEAR FILTERS" confirm_action.dialog_text = "Do you want to delete all filters?" -# confirm_action.connect("confirmed", filters, "TDO", [true], CONNECT_ONESHOT) -# confirm_action.show_modal(true) + confirm_action.connect("confirmed", stage, "clear_filters", [true], CONNECT_ONESHOT) + confirm_action.show_modal(true) func _menu_export_data_action(): @@ -83,11 +81,10 @@ func _menu_clear_data_action(): func _menu_about_action(): confirm_action.window_title = "FAKE DB" confirm_action.dialog_text = "About text here!" -# confirm_action.connect("confirmed", database, "fake_database", [true], CONNECT_ONESHOT) confirm_action.show_modal(true) - +# @DAM Hide this debug method before release. func _menu_fake_db_action(): confirm_action.window_title = "FAKE DB" confirm_action.dialog_text = "Do you want to delete all entries from the database and replace by fake entries?" diff --git a/readme.md b/readme.md index fc33cf1..0a85447 100644 --- a/readme.md +++ b/readme.md @@ -16,6 +16,8 @@ Surgery Log - [x] load/store database CSV file; - [x] export database to CSV file; - [x] allow to reset database; +- [ ] load/store filters CSV file; +- [ ] import/export filters to CSV file; - [ ] Implement file access permission check on Android: ```py if OS.get_name() == "Android": @@ -30,8 +32,6 @@ Surgery Log else: has_permissions = true ``` -- [ ] load/store filters CSV file; -- [ ] import/export filters to CSV file; - [ ] add auto-fill buttons on stage screen: - should show a pop-up with multiple options filtered according to current filters; - allow options to be scrolled by dragging; -- cgit v1.2.3 From 0eeacf3d142b40427ea5ca5b2806fe714c00f9b4 Mon Sep 17 00:00:00 2001 From: dam Date: Thu, 6 Jan 2022 11:39:06 +0000 Subject: Isolate database entry code to avoid cyclic references. --- logic/database.gd | 73 +++++-------------------------------------------- logic/database_entry.gd | 64 +++++++++++++++++++++++++++++++++++++++++++ logic/stage.gd | 2 +- project.godot | 6 ++++ 4 files changed, 78 insertions(+), 67 deletions(-) create mode 100644 logic/database_entry.gd (limited to 'logic/stage.gd') diff --git a/logic/database.gd b/logic/database.gd index 47cddbc..551b9bf 100644 --- a/logic/database.gd +++ b/logic/database.gd @@ -2,26 +2,6 @@ extends ItemList class_name Database const DATABASE_FILE_PATH: String = "user://database.csv" -const DATE_SEPARATOR: String = "-" -const DATE_FORMAT: String = "%04d-%02d-%02d" -const ENTRY_PROTOTYPE: Dictionary = { - "process_id": "", - "surgery_id": "", - "place": "", - "date": "", - "date_year": 0, - "date_month": 0, - "date_day": 0, - "anesthesia": "", - "first_assistant": "", - "type": "", - "sub_type": "", - "sub_sub_type": "", - "pathology": "", - "intervention": "", - "is_urgency": false, - "notes": "", -} const POINTER_VELOCITY_DECAYING_FACTOR: float = 2.5 var db: Array @@ -30,7 +10,7 @@ var staged_idx: int var is_pointer_dragging := false var pointer_drag_velocity := 0.0 -onready var stage := get_node("/root/main/stage") #as Stage # @DAM Solve cyclic load. +onready var stage := get_node("/root/main/stage") as Stage onready var delete_button := get_node("actions/delete") as Button onready var edit_button := get_node("actions/edit") as Button onready var add_button := get_node("actions/add") as Button @@ -85,7 +65,7 @@ func _notification(what: int): func get_entry_view(database_entry: Dictionary) -> String: - return "%6s | %6s | %s" % [database_entry.process_id, database_entry.surgery_id, get_entry_date(database_entry)] + return "%6s | %6s | %s" % [database_entry.process_id, database_entry.surgery_id, DatabaseEntry.get_entry_date(database_entry)] func item_selected(index: int): @@ -120,7 +100,7 @@ func edit_action(): func add_action(): self.visible = false stage.visible = true - var staged := instance_entry() + var staged := DatabaseEntry.instance_entry() stage.set_stage(staged) @@ -129,7 +109,7 @@ func scroll_down(): func save_stage(entry: Dictionary): - entry = instance_entry(entry) # @DAM Maybe we could not be creating endless dictionaries? + entry = DatabaseEntry.instance_entry(entry) # @DAM Maybe we could not be creating endless dictionaries? var next_selected_idx: int if staged_idx >= 0: db[staged_idx] = entry @@ -170,7 +150,7 @@ func load_database(file_path: String = DATABASE_FILE_PATH): is_first_line = false headers = csv_entry continue - var entry = ENTRY_PROTOTYPE.duplicate(true) + var entry = DatabaseEntry.instance_entry() for idx in headers.size(): var field_name := headers[idx] var field_value := csv_entry[idx] @@ -187,7 +167,7 @@ func load_database(file_path: String = DATABASE_FILE_PATH): func store_database(file_path: String = DATABASE_FILE_PATH): var file := File.new() file.open(file_path, File.WRITE) - var header := PoolStringArray(ENTRY_PROTOTYPE.keys()) + var header := PoolStringArray(DatabaseEntry.ENTRY_PROTOTYPE.keys()) file.store_csv_line(header) var entry := PoolStringArray() for it in db: @@ -203,52 +183,13 @@ func clear_database(save_changes: bool = false): store_database() -static func instance_entry(params: Dictionary = {}) -> Dictionary: - var new_entry := ENTRY_PROTOTYPE.duplicate(true) - new_entry.process_id = params.get("process_id", "") - new_entry.surgery_id = params.get("surgery_id", "") - new_entry.place = params.get("place", "") - - var today = OS.get_date() - new_entry.date_year = params.get("date_year", today.year) - new_entry.date_month = params.get("date_month", today.month) - new_entry.date_day = params.get("date_day", today.day) - new_entry.date = params.get("date", get_entry_date(new_entry)) # @DAM We should store only one version of the date. - - new_entry.anesthesia = params.get("anesthesia", "") - new_entry.first_assistant = params.get("first_assistant", "") - new_entry.type = params.get("type", "") - new_entry.sub_type = params.get("sub_type", "") - new_entry.sub_sub_type = params.get("sub_sub_type", "") - new_entry.pathology = params.get("pathology", "") - new_entry.intervention = params.get("intervention", "") - new_entry.is_urgency = params.get("is_urgency", false) - new_entry.notes = params.get("notes", "") - - return new_entry - - -static func get_entry_date(entry: Dictionary) -> String: - return DATE_FORMAT % [entry.date_year, entry.date_month, entry.date_day] - - -static func set_entry_date(entry: Dictionary, date: String): - date = date.strip_edges().replace(" ", DATE_SEPARATOR).replace("/", DATE_SEPARATOR).replace("\\", DATE_SEPARATOR) - var year_month_idx := date.find(DATE_SEPARATOR) - var month_day_idx := date.find(DATE_SEPARATOR, year_month_idx + 1) - entry.date = date - entry.date_year = int(date.substr(0, year_month_idx)) - entry.date_month = int(date.substr(year_month_idx + 1, month_day_idx - year_month_idx - 1)) - entry.date_day = int(date.substr(month_day_idx + 1)) - - func fake_database(save_changes: bool = false): clear_database() for idx in range(500): var date_year = 1 + int(float(idx) / 365.0) var date_month = idx % 12 var date_day = idx % 365 - var fake_entry = instance_entry({ + var fake_entry = DatabaseEntry.instance_entry({ "process_id": "%06d" % idx, "surgery_id": "s%05d" % idx, "date": "%04d-%02d-%02d" % [date_year, date_month, date_day] diff --git a/logic/database_entry.gd b/logic/database_entry.gd new file mode 100644 index 0000000..19f2255 --- /dev/null +++ b/logic/database_entry.gd @@ -0,0 +1,64 @@ +extends Reference +class_name DatabaseEntry + +const DATE_SEPARATOR: String = "-" +const DATE_FORMAT: String = "%04d-%02d-%02d" +const ENTRY_PROTOTYPE: Dictionary = { + "process_id": "", + "surgery_id": "", + "place": "", + "date": "", + "date_year": 0, + "date_month": 0, + "date_day": 0, + "anesthesia": "", + "first_assistant": "", + "type": "", + "sub_type": "", + "sub_sub_type": "", + "pathology": "", + "intervention": "", + "is_urgency": false, + "notes": "", +} + + +static func instance_entry(params: Dictionary = {}) -> Dictionary: + var new_entry := ENTRY_PROTOTYPE.duplicate(true) + new_entry.process_id = params.get("process_id", "") + new_entry.surgery_id = params.get("surgery_id", "") + new_entry.place = params.get("place", "") + + var today = OS.get_date() + new_entry.date_year = params.get("date_year", today.year) + new_entry.date_month = params.get("date_month", today.month) + new_entry.date_day = params.get("date_day", today.day) + new_entry.date = params.get("date", get_entry_date(new_entry)) # @DAM We should store only one version of the date. + + new_entry.anesthesia = params.get("anesthesia", "") + new_entry.first_assistant = params.get("first_assistant", "") + new_entry.type = params.get("type", "") + new_entry.sub_type = params.get("sub_type", "") + new_entry.sub_sub_type = params.get("sub_sub_type", "") + new_entry.pathology = params.get("pathology", "") + new_entry.intervention = params.get("intervention", "") + new_entry.is_urgency = params.get("is_urgency", false) + new_entry.notes = params.get("notes", "") + + return new_entry + + +static func get_entry_date(entry: Dictionary) -> String: + return DATE_FORMAT % [entry.date_year, entry.date_month, entry.date_day] + + +static func set_entry_date(entry: Dictionary, date: String): + date = date.strip_edges().replace(" ", DATE_SEPARATOR).replace("/", DATE_SEPARATOR).replace("\\", DATE_SEPARATOR) + var year_month_idx := date.find(DATE_SEPARATOR) + var month_day_idx := date.find(DATE_SEPARATOR, year_month_idx + 1) + entry.date = date + entry.date_year = int(date.substr(0, year_month_idx)) + entry.date_month = int(date.substr(year_month_idx + 1, month_day_idx - year_month_idx - 1)) + entry.date_day = int(date.substr(month_day_idx + 1)) + + diff --git a/logic/stage.gd b/logic/stage.gd index dd345e8..511068e 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -173,7 +173,7 @@ func load_filters(file_path: String = FILTERS_FILE_PATH): is_first_line = false headers = csv_entry continue - var entry = Database.ENTRY_PROTOTYPE.duplicate(true) +# var entry = DatabaseEntry.instance_entry() # for idx in headers.size(): # var field_name := headers[idx] # var field_value := csv_entry[idx] diff --git a/project.godot b/project.godot index f5dbe7a..cfdcbb8 100644 --- a/project.godot +++ b/project.godot @@ -14,6 +14,11 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://logic/database.gd" }, { +"base": "Reference", +"class": "DatabaseEntry", +"language": "GDScript", +"path": "res://logic/database_entry.gd" +}, { "base": "Control", "class": "DatePicker", "language": "GDScript", @@ -36,6 +41,7 @@ _global_script_classes=[ { } ] _global_script_class_icons={ "Database": "", +"DatabaseEntry": "", "DatePicker": "", "PointerInputSensor": "", "Stage": "", -- cgit v1.2.3 From 431f042390ad36297a5ec986772c77da23b7fb67 Mon Sep 17 00:00:00 2001 From: dam Date: Thu, 6 Jan 2022 15:42:44 +0000 Subject: Remove date field and reduce usage of dictionary instances. --- logic/database.gd | 17 +++++++------ logic/database_entry.gd | 20 +++++++++++---- logic/stage.gd | 67 ++++++++++++++++++++++++------------------------- 3 files changed, 58 insertions(+), 46 deletions(-) (limited to 'logic/stage.gd') diff --git a/logic/database.gd b/logic/database.gd index 551b9bf..044681f 100644 --- a/logic/database.gd +++ b/logic/database.gd @@ -77,6 +77,10 @@ func clear_selection(): unselect_all() +func scroll_to_selected(): + v_scroll_bar.value = float(selected_idx)/float(db.size()) * v_scroll_bar.max_value - (v_scroll_bar.page * 0.5) + + func delete_action(): if selected_idx < 0: return @@ -104,12 +108,11 @@ func add_action(): stage.set_stage(staged) -func scroll_down(): - v_scroll_bar.value = v_scroll_bar.max_value - - func save_stage(entry: Dictionary): - entry = DatabaseEntry.instance_entry(entry) # @DAM Maybe we could not be creating endless dictionaries? + if DatabaseEntry.is_valid_entry(entry) == false: + printerr("INVALID ENTRY RECEIVED") + return # @DAM Deal with this. + var next_selected_idx: int if staged_idx >= 0: db[staged_idx] = entry @@ -117,13 +120,12 @@ func save_stage(entry: Dictionary): else: db.append(entry) add_item(get_entry_view(entry)) - call_deferred("scroll_down") next_selected_idx = db.size() - 1 select(next_selected_idx) emit_signal("item_selected", next_selected_idx) # Calling "select" does not trigger the "item_selected" signal. set_item_text(selected_idx, get_entry_view(db[selected_idx])) - v_scroll_bar.value = float(selected_idx)/float(db.size()) * v_scroll_bar.max_value - (v_scroll_bar.page * 0.5) + call_deferred("scroll_to_selected") store_database() @@ -171,6 +173,7 @@ func store_database(file_path: String = DATABASE_FILE_PATH): file.store_csv_line(header) var entry := PoolStringArray() for it in db: + # @DAM This approach depends on the order the dictionary fields are created. file.store_csv_line(it.values()) file.close() diff --git a/logic/database_entry.gd b/logic/database_entry.gd index 19f2255..496b752 100644 --- a/logic/database_entry.gd +++ b/logic/database_entry.gd @@ -6,11 +6,10 @@ const DATE_FORMAT: String = "%04d-%02d-%02d" const ENTRY_PROTOTYPE: Dictionary = { "process_id": "", "surgery_id": "", - "place": "", - "date": "", "date_year": 0, "date_month": 0, "date_day": 0, + "place": "", "anesthesia": "", "first_assistant": "", "type": "", @@ -27,14 +26,13 @@ static func instance_entry(params: Dictionary = {}) -> Dictionary: var new_entry := ENTRY_PROTOTYPE.duplicate(true) new_entry.process_id = params.get("process_id", "") new_entry.surgery_id = params.get("surgery_id", "") - new_entry.place = params.get("place", "") var today = OS.get_date() new_entry.date_year = params.get("date_year", today.year) new_entry.date_month = params.get("date_month", today.month) new_entry.date_day = params.get("date_day", today.day) - new_entry.date = params.get("date", get_entry_date(new_entry)) # @DAM We should store only one version of the date. + new_entry.place = params.get("place", "") new_entry.anesthesia = params.get("anesthesia", "") new_entry.first_assistant = params.get("first_assistant", "") new_entry.type = params.get("type", "") @@ -48,6 +46,19 @@ static func instance_entry(params: Dictionary = {}) -> Dictionary: return new_entry +static func is_valid_entry(entry: Dictionary) -> bool: + var is_valid: bool + + is_valid = entry.has_all(ENTRY_PROTOTYPE.keys()) && ENTRY_PROTOTYPE.keys().size() == entry.keys().size() + + for it in ENTRY_PROTOTYPE.keys(): + if typeof(ENTRY_PROTOTYPE[it]) != typeof(entry[it]): + is_valid = false + break + + return is_valid + + static func get_entry_date(entry: Dictionary) -> String: return DATE_FORMAT % [entry.date_year, entry.date_month, entry.date_day] @@ -56,7 +67,6 @@ static func set_entry_date(entry: Dictionary, date: String): date = date.strip_edges().replace(" ", DATE_SEPARATOR).replace("/", DATE_SEPARATOR).replace("\\", DATE_SEPARATOR) var year_month_idx := date.find(DATE_SEPARATOR) var month_day_idx := date.find(DATE_SEPARATOR, year_month_idx + 1) - entry.date = date entry.date_year = int(date.substr(0, year_month_idx)) entry.date_month = int(date.substr(year_month_idx + 1, month_day_idx - year_month_idx - 1)) entry.date_day = int(date.substr(month_day_idx + 1)) diff --git a/logic/stage.gd b/logic/stage.gd index 511068e..f6f6e47 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -7,9 +7,10 @@ signal discard # () const FILTERS_FILE_PATH: String = "user://filters.csv" const POINTER_VELOCITY_DECAYING_FACTOR: float = 2.5 +var staged_entry := {} +var filters := {} var is_pointer_dragging := false var pointer_drag_velocity := 0.0 -var filters := {} onready var process_id := get_node("controls/process_id") as LineEdit onready var surgery_id := get_node("controls/surgery_id") as LineEdit @@ -79,42 +80,40 @@ func discard_action(): func set_stage(entry: Dictionary): - process_id.text = entry.process_id - surgery_id.text = entry.surgery_id - date.set_date(entry.date_year, entry.date_month, entry.date_day) - place.text = entry.place - anesthesia.text = entry.anesthesia - first_assistant.text = entry.first_assistant - type.text = entry.type - sub_type.text = entry.sub_type - sub_sub_type.text = entry.sub_sub_type - pathology.text = entry.pathology - intervention.text = entry.intervention - is_urgency.pressed = entry.is_urgency - notes.text = entry.notes - self.scroll_vertical = 0 # @DAM TODO + staged_entry = entry.duplicate(true) + process_id.text = staged_entry.process_id + surgery_id.text = staged_entry.surgery_id + date.set_date(staged_entry.date_year, staged_entry.date_month, staged_entry.date_day) + place.text = staged_entry.place + anesthesia.text = staged_entry.anesthesia + first_assistant.text = staged_entry.first_assistant + type.text = staged_entry.type + sub_type.text = staged_entry.sub_type + sub_sub_type.text = staged_entry.sub_sub_type + pathology.text = staged_entry.pathology + intervention.text = staged_entry.intervention + is_urgency.pressed = staged_entry.is_urgency + notes.text = staged_entry.notes + self.scroll_vertical = 0 func get_stage() -> Dictionary: - # @DAM Simplify all this... avoid creating multiple entries/dictionaries. - var entry: Dictionary = { - "process_id": process_id.text, - "surgery_id": surgery_id.text, - "date_year": date.get_year(), - "date_month": date.get_month(), - "date_day": date.get_day(), - "place": place.text, - "anesthesia": anesthesia.text, - "first_assistant": first_assistant.text, - "type": type.text, - "sub_type": sub_type.text, - "sub_sub_type": sub_sub_type.text, - "pathology": pathology.text, - "intervention": intervention.text, - "is_urgency": is_urgency.pressed, - "notes": notes.text, - } - return entry + staged_entry.process_id = process_id.text + staged_entry.surgery_id = surgery_id.text + staged_entry.date_year = date.get_year() + staged_entry.date_month = date.get_month() + staged_entry.date_day = date.get_day() + staged_entry.place = place.text + staged_entry.anesthesia = anesthesia.text + staged_entry.first_assistant= first_assistant.text + staged_entry.type = type.text + staged_entry.sub_type = sub_type.text + staged_entry.sub_sub_type = sub_sub_type.text + staged_entry.pathology = pathology.text + staged_entry.intervention = intervention.text + staged_entry.is_urgency = is_urgency.pressed + staged_entry.notes = notes.text + return staged_entry func _notification(what: int): -- cgit v1.2.3 From 5ba40857cdc770841d216a27e2a9e8bb3ebf3186 Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 9 Jan 2022 01:25:55 +0000 Subject: Split touch logic from database. Prototype of popup with list of options. --- logic/database.gd | 67 ++------------------------------ logic/popup_list.gd | 24 ++++++++++++ logic/stage.gd | 75 ++++++++++++++++++++++++++++++++---- main.tscn | 39 ++++++++++--------- pointer_input_sensor.gd | 22 +++++++++++ project.godot | 8 +++- readme.md | 2 + touch_item_list/touch_item_list.gd | 70 +++++++++++++++++++++++++++++++++ touch_item_list/touch_item_list.tscn | 23 +++++++++++ 9 files changed, 240 insertions(+), 90 deletions(-) create mode 100644 logic/popup_list.gd create mode 100644 touch_item_list/touch_item_list.gd create mode 100644 touch_item_list/touch_item_list.tscn (limited to 'logic/stage.gd') diff --git a/logic/database.gd b/logic/database.gd index 044681f..de978b7 100644 --- a/logic/database.gd +++ b/logic/database.gd @@ -1,21 +1,16 @@ -extends ItemList +extends TouchItemList class_name Database const DATABASE_FILE_PATH: String = "user://database.csv" -const POINTER_VELOCITY_DECAYING_FACTOR: float = 2.5 var db: Array var selected_idx: int var staged_idx: int -var is_pointer_dragging := false -var pointer_drag_velocity := 0.0 onready var stage := get_node("/root/main/stage") as Stage onready var delete_button := get_node("actions/delete") as Button onready var edit_button := get_node("actions/edit") as Button onready var add_button := get_node("actions/add") as Button -onready var v_scroll_bar := get_v_scroll() as ScrollBar -onready var drag_sensor := get_node("drag_sensor") as PointerInputSensor func _init(): @@ -36,24 +31,13 @@ func _ready(): stage.connect("save", self, "save_stage") stage.connect("discard", self, "discard_stage") - drag_sensor.connect("on_press", self, "pointer_input_handler") - drag_sensor.connect("on_drag", self, "pointer_input_handler") - drag_sensor.connect("on_end_drag", self, "pointer_input_handler") - drag_sensor.connect("on_click", self, "pointer_input_handler") - for it in db: self.add_item(get_entry_view(it)) -func _process(delta: float): - # Apply drag movement inertia. - if is_pointer_dragging == false && abs(pointer_drag_velocity) > 0.5: - pointer_drag_velocity *= clamp((1.0 - POINTER_VELOCITY_DECAYING_FACTOR * delta), 0.0, 1.0) - v_scroll_bar.value -= pointer_drag_velocity * delta - - func _notification(what: int): - # @DAM Yet to be polished. + # @DAM This code should be moved into the main.gd which should check which node was currently + # active and above, and send the signal there. if visible == false || has_focus() == false: return @@ -77,10 +61,6 @@ func clear_selection(): unselect_all() -func scroll_to_selected(): - v_scroll_bar.value = float(selected_idx)/float(db.size()) * v_scroll_bar.max_value - (v_scroll_bar.page * 0.5) - - func delete_action(): if selected_idx < 0: return @@ -125,7 +105,7 @@ func save_stage(entry: Dictionary): select(next_selected_idx) emit_signal("item_selected", next_selected_idx) # Calling "select" does not trigger the "item_selected" signal. set_item_text(selected_idx, get_entry_view(db[selected_idx])) - call_deferred("scroll_to_selected") + ensure_current_is_visible() store_database() @@ -203,42 +183,3 @@ func fake_database(save_changes: bool = false): store_database() -func pointer_input_handler(pointer: PointerInputSensor.PointerInputData): - match pointer.action: - PointerInputSensor.PointerInputAction.ON_PRESS: - is_pointer_dragging = true - - PointerInputSensor.PointerInputAction.ON_DRAG: - is_pointer_dragging = true - pointer_drag_velocity = pointer.velocity.y - v_scroll_bar.value -= pointer.relative_position.y - - PointerInputSensor.PointerInputAction.ON_END_DRAG: - is_pointer_dragging = false - - PointerInputSensor.PointerInputAction.ON_CLICK: - var target := self - var position := target.get_global_mouse_position() - target.rect_global_position - - var event_touch := InputEventScreenTouch.new() - event_touch.index = 0 - event_touch.position = position - - var event_mouse := InputEventMouseButton.new() - event_mouse.button_index = BUTTON_LEFT - event_mouse.button_mask = BUTTON_MASK_LEFT - event_mouse.position = position - - event_mouse.pressed = true - event_touch.pressed = true - target._gui_input(event_mouse) - target._gui_input(event_touch) - - target.grab_focus() - - event_mouse.pressed = false - event_touch.pressed = false - target._gui_input(event_mouse) - target._gui_input(event_touch) - - diff --git a/logic/popup_list.gd b/logic/popup_list.gd new file mode 100644 index 0000000..1dd156c --- /dev/null +++ b/logic/popup_list.gd @@ -0,0 +1,24 @@ +extends Popup + +signal item_selected + +onready var item_list := get_node("list") as TouchItemList + + +func _ready(): + item_list.connect("item_selected", self, "selected") + + +func popup_options(options: Array): + item_list.v_scroll_bar.value = 0.0 + item_list.clear() + for it in options: + item_list.add_item(it) + self.popup_centered_ratio(0.9) + + +func selected(index: int): + self.hide() + emit_signal("item_selected", index) + + diff --git a/logic/stage.gd b/logic/stage.gd index f6f6e47..dc42273 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -8,7 +8,42 @@ const FILTERS_FILE_PATH: String = "user://filters.csv" const POINTER_VELOCITY_DECAYING_FACTOR: float = 2.5 var staged_entry := {} -var filters := {} +var filters := { + "place": { + "bloco central": null, + "tondela": null, + "xpto_00": null, + "xpto_01": null, + "xpto_02": null, + "xpto_03": null, + "xpto_04": null, + "xpto_05": null, + "xpto_06": null, + "xpto_07": null, + "xpto_08": null, + "xpto_09": null, + "xpto_10": null, + "xpto_11": null, + "xpto_12": null, + "xpto_13": null, + "xpto_14": null, + "xpto_15": null, + "xpto_16": null, + "xpto_17": null, + "xpto_18": null, + "xpto_19": null, + "xpto_20": null, + "xpto_21": null, + "xpto_22": null, + "xpto_23": null, + "xpto_24": null, + "xpto_25": null, + "xpto_26": null, + "xpto_27": null, + "xpto_28": null, + "xpto_29": null, + }, +} var is_pointer_dragging := false var pointer_drag_velocity := 0.0 @@ -60,6 +95,22 @@ func _ready(): if it is LineEdit: it.connect("focus_exited", it, "deselect") + + var auto_place := place.get_node("auto") as Button + auto_place.connect("pressed", self, "auto_populate", ["place"]) + + +func auto_populate(field: String): + var stage_options = get_node("/root/main/popup_list") as Popup + stage_options.connect("item_selected", self, "auto_selected", [field], CONNECT_ONESHOT) + stage_options.popup_options(filters[field].keys()) + + +func auto_selected(index: int, field: String): + var field_input: LineEdit = self[field] + field_input.text = filters[field].keys()[index] + field_input.caret_position = field_input.text.length() + func _process(delta: float): @@ -116,11 +167,6 @@ func get_stage() -> Dictionary: return staged_entry -func _notification(what: int): - if what == MainLoop.NOTIFICATION_WM_GO_BACK_REQUEST: - discard_action() - - func pointer_input_handler(pointer: PointerInputSensor.PointerInputData): match pointer.action: PointerInputSensor.PointerInputAction.ON_PRESS: @@ -135,9 +181,23 @@ func pointer_input_handler(pointer: PointerInputSensor.PointerInputData): is_pointer_dragging = false PointerInputSensor.PointerInputAction.ON_CLICK: + + # @DAM Try this approach on the TouchItemList + var target: Control = pointer.target.get_parent() var position := target.get_global_mouse_position() - target.rect_global_position + var button: Button = target.get_node("auto") + if button != null && button.get_rect().has_point(position): + button.grab_focus() +# button.emit_signal("button_down") + button.emit_signal("pressed") +# button.emit_signal("button_up") + else: + target.grab_focus() + return + + var event_touch := InputEventScreenTouch.new() event_touch.index = 0 event_touch.position = position @@ -153,13 +213,14 @@ func pointer_input_handler(pointer: PointerInputSensor.PointerInputData): target._gui_input(event_touch) target.grab_focus() - + event_mouse.pressed = false event_touch.pressed = false target._gui_input(event_mouse) target._gui_input(event_touch) + func load_filters(file_path: String = FILTERS_FILE_PATH): var file := File.new() file.open(file_path, File.READ_WRITE) diff --git a/main.tscn b/main.tscn index d4ff096..52d9ccf 100644 --- a/main.tscn +++ b/main.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=11 format=2] +[gd_scene load_steps=12 format=2] [ext_resource path="res://main.gd" type="Script" id=1] [ext_resource path="res://date_picker/date_picker.tscn" type="PackedScene" id=2] @@ -9,7 +9,8 @@ [ext_resource path="res://icons/add.png" type="Texture" id=7] [ext_resource path="res://icons/delete.png" type="Texture" id=8] [ext_resource path="res://logic/stage.gd" type="Script" id=9] -[ext_resource path="res://pointer_input_sensor.gd" type="Script" id=10] +[ext_resource path="res://logic/popup_list.gd" type="Script" id=10] +[ext_resource path="res://touch_item_list/touch_item_list.tscn" type="PackedScene" id=11] [node name="main" type="Control"] anchor_right = 1.0 @@ -20,24 +21,8 @@ __meta__ = { "_edit_use_anchors_": false } -[node name="database" type="ItemList" parent="."] -anchor_right = 1.0 -anchor_bottom = 1.0 -margin_top = 60.0 -mouse_filter = 2 +[node name="database" parent="." instance=ExtResource( 11 )] script = ExtResource( 5 ) -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="drag_sensor" type="Control" parent="database"] -anchor_right = 1.0 -anchor_bottom = 1.0 -margin_right = -8.0 -script = ExtResource( 10 ) -__meta__ = { -"_edit_use_anchors_": false -} [node name="actions" type="VBoxContainer" parent="database"] anchor_left = 1.0 @@ -133,6 +118,12 @@ placeholder_text = "Local" caret_blink = true caret_blink_speed = 0.5 +[node name="auto" type="Button" parent="stage/controls/place"] +anchor_left = 0.85 +anchor_right = 1.0 +anchor_bottom = 1.0 +text = "X" + [node name="anesthesia" type="LineEdit" parent="stage/controls"] margin_top = 363.0 margin_right = 1080.0 @@ -243,6 +234,7 @@ anchor_bottom = 1.0 size_flags_horizontal = 3 size_flags_vertical = 3 window_title = "PICK FILE" +mode_overrides_title = false access = 2 filters = PoolStringArray( "*.csv" ) show_hidden_files = true @@ -261,6 +253,15 @@ window_title = "CONFIRM ACTION" dialog_text = "Are you sure you want to delete all filters?" dialog_autowrap = true +[node name="popup_list" type="Popup" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +popup_exclusive = true +script = ExtResource( 10 ) + +[node name="list" parent="popup_list" instance=ExtResource( 11 )] +margin_top = 0.0 + [node name="debug" type="Label" parent="."] visible = false anchor_right = 1.0 diff --git a/pointer_input_sensor.gd b/pointer_input_sensor.gd index 27f257c..ee5e390 100644 --- a/pointer_input_sensor.gd +++ b/pointer_input_sensor.gd @@ -12,6 +12,7 @@ signal on_exit signal on_begin_drag signal on_drag signal on_end_drag +signal on_scroll enum PointerInputAction { UNDEFINED, @@ -25,10 +26,12 @@ enum PointerInputAction { ON_BEGIN_DRAG, ON_DRAG, ON_END_DRAG, + ON_SCROLL, } class PointerInputData: var target: PointerInputSensor + var event: InputEvent var index := -1 var initial_position := Vector2.ZERO var current_position := Vector2.ZERO @@ -36,8 +39,10 @@ class PointerInputData: var velocity := Vector2.ZERO var was_dragged := false var is_pressed := false + var scroll := 0.0 var action: int = PointerInputAction.UNDEFINED + export var drag_threshold_cm: float = 0.250 var pointer: PointerInputData @@ -64,6 +69,12 @@ func _gui_input(event: InputEvent): if event.index != 0: return + pointer.event = event +# pointer.index = -1 +# pointer.scroll = 0.0 +# pointer.action = PointerInputAction.UNDEFINED +# pointer.velocity = Vector2.ZERO + if event is InputEventScreenTouch && (pointer.is_pressed == false || pointer.index == event.index): var touch := event as InputEventScreenTouch pointer.is_pressed = event.pressed @@ -84,6 +95,10 @@ func _gui_input(event: InputEvent): emit_signal("on_end_drag", pointer) pointer.index = -1 pointer.was_dragged = false + # @DAM Maybe add this. +# pointer.initial_position = Vector2.ZERO +# pointer.current_position = Vector2.ZERO +# pointer.relative_position = Vector2.ZERO if event is InputEventScreenDrag && event.index == pointer.index: var drag := event as InputEventScreenDrag @@ -96,5 +111,12 @@ func _gui_input(event: InputEvent): emit_signal("on_begin_drag", pointer) pointer.action = PointerInputAction.ON_DRAG emit_signal("on_drag", pointer) + + # @DAM Clean this code please. + if event is InputEventMouseButton && event.is_pressed(): + if event.button_index == BUTTON_WHEEL_UP || event.button_index == BUTTON_WHEEL_DOWN: + pointer.scroll = -1.0 if event.button_index == BUTTON_WHEEL_UP else 1.0 + pointer.action = PointerInputAction.ON_SCROLL + emit_signal("on_scroll", pointer) diff --git a/project.godot b/project.godot index cfdcbb8..d630b8d 100644 --- a/project.godot +++ b/project.godot @@ -9,7 +9,7 @@ config_version=4 _global_script_classes=[ { -"base": "ItemList", +"base": "TouchItemList", "class": "Database", "language": "GDScript", "path": "res://logic/database.gd" @@ -34,6 +34,11 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://logic/stage.gd" }, { +"base": "ItemList", +"class": "TouchItemList", +"language": "GDScript", +"path": "res://touch_item_list/touch_item_list.gd" +}, { "base": "Control", "class": "ValuePicker", "language": "GDScript", @@ -45,6 +50,7 @@ _global_script_class_icons={ "DatePicker": "", "PointerInputSensor": "", "Stage": "", +"TouchItemList": "", "ValuePicker": "" } diff --git a/readme.md b/readme.md index 0a85447..6a341e3 100644 --- a/readme.md +++ b/readme.md @@ -16,6 +16,8 @@ Surgery Log - [x] load/store database CSV file; - [x] export database to CSV file; - [x] allow to reset database; +- [x] split touch logic from database (create TouchItemList); +- [ ] split touch logic from stage (create TouchVerticalContainer); - [ ] load/store filters CSV file; - [ ] import/export filters to CSV file; - [ ] Implement file access permission check on Android: diff --git a/touch_item_list/touch_item_list.gd b/touch_item_list/touch_item_list.gd new file mode 100644 index 0000000..d2d0d37 --- /dev/null +++ b/touch_item_list/touch_item_list.gd @@ -0,0 +1,70 @@ +extends ItemList +class_name TouchItemList + +const POINTER_VELOCITY_DECAYING_FACTOR: float = 2.5 + +var is_pointer_dragging := false +var pointer_drag_velocity := 0.0 + +onready var drag_sensor := get_node("drag_sensor") as PointerInputSensor +onready var v_scroll_bar := get_v_scroll() as ScrollBar + + +func _ready(): + drag_sensor.connect("on_press", self, "pointer_input_handler") + drag_sensor.connect("on_drag", self, "pointer_input_handler") + drag_sensor.connect("on_end_drag", self, "pointer_input_handler") + drag_sensor.connect("on_click", self, "pointer_input_handler") + drag_sensor.connect("on_scroll", self, "pointer_input_handler") + + +func _process(delta: float): + # Apply drag movement inertia. + if is_pointer_dragging == false && abs(pointer_drag_velocity) > 0.5: + pointer_drag_velocity *= clamp((1.0 - POINTER_VELOCITY_DECAYING_FACTOR * delta), 0.0, 1.0) + v_scroll_bar.value -= pointer_drag_velocity * delta + + +func pointer_input_handler(pointer: PointerInputSensor.PointerInputData): + match pointer.action: + PointerInputSensor.PointerInputAction.ON_PRESS: + is_pointer_dragging = true + + PointerInputSensor.PointerInputAction.ON_DRAG: + is_pointer_dragging = true + pointer_drag_velocity = pointer.velocity.y + v_scroll_bar.value -= pointer.relative_position.y + + PointerInputSensor.PointerInputAction.ON_END_DRAG: + is_pointer_dragging = false + + PointerInputSensor.PointerInputAction.ON_SCROLL: + var target := self + target._gui_input(pointer.event) + + PointerInputSensor.PointerInputAction.ON_CLICK: + var target := self + var position := target.get_global_mouse_position() - target.rect_global_position + + var event_touch := InputEventScreenTouch.new() + event_touch.index = 0 + event_touch.position = position + + var event_mouse := InputEventMouseButton.new() + event_mouse.button_index = BUTTON_LEFT + event_mouse.button_mask = BUTTON_MASK_LEFT + event_mouse.position = position + + event_mouse.pressed = true + event_touch.pressed = true + target._gui_input(event_mouse) + target._gui_input(event_touch) + + target.grab_focus() + + event_mouse.pressed = false + event_touch.pressed = false + target._gui_input(event_mouse) + target._gui_input(event_touch) + + diff --git a/touch_item_list/touch_item_list.tscn b/touch_item_list/touch_item_list.tscn new file mode 100644 index 0000000..6022ed4 --- /dev/null +++ b/touch_item_list/touch_item_list.tscn @@ -0,0 +1,23 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://pointer_input_sensor.gd" type="Script" id=1] +[ext_resource path="res://touch_item_list/touch_item_list.gd" type="Script" id=2] + +[node name="item_list" type="ItemList"] +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_top = 60.0 +mouse_filter = 2 +script = ExtResource( 2 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="drag_sensor" type="Control" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_right = -8.0 +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} -- cgit v1.2.3 From 81b602620412fbad0429e841051c30c537a1b461 Mon Sep 17 00:00:00 2001 From: dam Date: Mon, 10 Jan 2022 17:56:30 +0000 Subject: Split touch logic from stage --- logic/stage.gd | 99 ++-------------------- main.tscn | 48 +++++------ project.godot | 8 +- readme.md | 5 +- touch_item_list/touch_item_list.gd | 96 +++++++++++---------- touch_item_list/touch_item_list.tscn | 2 +- .../touch_vertical_container.gd | 78 +++++++++++++++++ .../touch_vertical_container.tscn | 18 ++++ 8 files changed, 183 insertions(+), 171 deletions(-) create mode 100644 touch_vertical_container/touch_vertical_container.gd create mode 100644 touch_vertical_container/touch_vertical_container.tscn (limited to 'logic/stage.gd') diff --git a/logic/stage.gd b/logic/stage.gd index dc42273..12457c9 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -1,11 +1,10 @@ -extends ScrollContainer +extends TouchVerticalContainer class_name Stage signal save # (database_entry: Dictionary) signal discard # () const FILTERS_FILE_PATH: String = "user://filters.csv" -const POINTER_VELOCITY_DECAYING_FACTOR: float = 2.5 var staged_entry := {} var filters := { @@ -44,8 +43,6 @@ var filters := { "xpto_29": null, }, } -var is_pointer_dragging := false -var pointer_drag_velocity := 0.0 onready var process_id := get_node("controls/process_id") as LineEdit onready var surgery_id := get_node("controls/surgery_id") as LineEdit @@ -62,40 +59,16 @@ onready var is_urgency := get_node("controls/is_urgency") as Button onready var notes := get_node("controls/notes") as LineEdit onready var save_button := get_node("controls/save") as Button onready var discard_button := get_node("controls/discard") as Button -onready var v_scroll_bar := get_v_scrollbar() as VScrollBar + + +func _init(): + exclude_controls = ["date_picker", "save", "discard"] func _ready(): save_button.connect("pressed", self, "save_action") discard_button.connect("pressed", self, "discard_action") - for it in get_node("controls").get_children(): - it = it as Control - match it.name: - "date_picker", "save", "discard": - pass - - _: - var drag_sensor = PointerInputSensor.new() - it.add_child(drag_sensor) - - drag_sensor.name = "drag_sensor" - drag_sensor.anchor_right = 1.0 - drag_sensor.anchor_bottom = 1.0 - - drag_sensor.connect("on_press", self, "pointer_input_handler") - drag_sensor.connect("on_drag", self, "pointer_input_handler") - drag_sensor.connect("on_end_drag", self, "pointer_input_handler") - drag_sensor.connect("on_click", self, "pointer_input_handler") - - it.connect("focus_entered", drag_sensor, "set_mouse_filter", [Control.MOUSE_FILTER_IGNORE]) - it.connect("focus_entered", drag_sensor, "mouse_default_cursor_shape", [Control.CURSOR_IBEAM]) - it.connect("focus_exited", drag_sensor, "set_mouse_filter", [Control.MOUSE_FILTER_STOP]) - it.connect("focus_exited", drag_sensor, "mouse_default_cursor_shape", [Control.CURSOR_ARROW]) - - if it is LineEdit: - it.connect("focus_exited", it, "deselect") - var auto_place := place.get_node("auto") as Button auto_place.connect("pressed", self, "auto_populate", ["place"]) @@ -112,14 +85,6 @@ func auto_selected(index: int, field: String): field_input.caret_position = field_input.text.length() - -func _process(delta: float): - # Apply drag movement inertia. - if is_pointer_dragging == false && abs(pointer_drag_velocity) > 0.5: - pointer_drag_velocity *= clamp((1.0 - POINTER_VELOCITY_DECAYING_FACTOR * delta), 0.0, 1.0) - v_scroll_bar.value -= pointer_drag_velocity * delta - - func save_action(): self.visible = false emit_signal("save", get_stage()) @@ -167,60 +132,6 @@ func get_stage() -> Dictionary: return staged_entry -func pointer_input_handler(pointer: PointerInputSensor.PointerInputData): - match pointer.action: - PointerInputSensor.PointerInputAction.ON_PRESS: - is_pointer_dragging = true - - PointerInputSensor.PointerInputAction.ON_DRAG: - is_pointer_dragging = true - pointer_drag_velocity = pointer.velocity.y - v_scroll_bar.value -= pointer.relative_position.y - - PointerInputSensor.PointerInputAction.ON_END_DRAG: - is_pointer_dragging = false - - PointerInputSensor.PointerInputAction.ON_CLICK: - - # @DAM Try this approach on the TouchItemList - - var target: Control = pointer.target.get_parent() - var position := target.get_global_mouse_position() - target.rect_global_position - - var button: Button = target.get_node("auto") - if button != null && button.get_rect().has_point(position): - button.grab_focus() -# button.emit_signal("button_down") - button.emit_signal("pressed") -# button.emit_signal("button_up") - else: - target.grab_focus() - return - - - var event_touch := InputEventScreenTouch.new() - event_touch.index = 0 - event_touch.position = position - - var event_mouse := InputEventMouseButton.new() - event_mouse.button_index = BUTTON_LEFT - event_mouse.button_mask = BUTTON_MASK_LEFT - event_mouse.position = position - - event_mouse.pressed = true - event_touch.pressed = true - target._gui_input(event_mouse) - target._gui_input(event_touch) - - target.grab_focus() - - event_mouse.pressed = false - event_touch.pressed = false - target._gui_input(event_mouse) - target._gui_input(event_touch) - - - func load_filters(file_path: String = FILTERS_FILE_PATH): var file := File.new() file.open(file_path, File.READ_WRITE) diff --git a/main.tscn b/main.tscn index 52d9ccf..45cacd4 100644 --- a/main.tscn +++ b/main.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=12 format=2] +[gd_scene load_steps=13 format=2] [ext_resource path="res://main.gd" type="Script" id=1] [ext_resource path="res://date_picker/date_picker.tscn" type="PackedScene" id=2] @@ -11,6 +11,7 @@ [ext_resource path="res://logic/stage.gd" type="Script" id=9] [ext_resource path="res://logic/popup_list.gd" type="Script" id=10] [ext_resource path="res://touch_item_list/touch_item_list.tscn" type="PackedScene" id=11] +[ext_resource path="res://touch_vertical_container/touch_vertical_container.tscn" type="PackedScene" id=12] [node name="main" type="Control"] anchor_right = 1.0 @@ -70,31 +71,22 @@ __meta__ = { "_edit_use_anchors_": false } -[node name="stage" type="ScrollContainer" parent="."] +[node name="stage" parent="." instance=ExtResource( 12 )] visible = false -anchor_right = 1.0 -anchor_bottom = 1.0 margin_top = 60.0 -scroll_horizontal_enabled = false script = ExtResource( 9 ) -__meta__ = { -"_edit_use_anchors_": false -} -[node name="controls" type="VBoxContainer" parent="stage"] -margin_right = 1080.0 +[node name="controls" parent="stage" index="0"] margin_bottom = 1860.0 -size_flags_horizontal = 3 -size_flags_vertical = 3 -[node name="process_id" type="LineEdit" parent="stage/controls"] +[node name="process_id" type="LineEdit" parent="stage/controls" index="0"] margin_right = 1080.0 margin_bottom = 49.0 placeholder_text = "Nº Processo" caret_blink = true caret_blink_speed = 0.5 -[node name="surgery_id" type="LineEdit" parent="stage/controls"] +[node name="surgery_id" type="LineEdit" parent="stage/controls" index="1"] margin_top = 53.0 margin_right = 1080.0 margin_bottom = 102.0 @@ -102,7 +94,7 @@ placeholder_text = "Nº Cirurgia" caret_blink = true caret_blink_speed = 0.5 -[node name="date_picker" parent="stage/controls" instance=ExtResource( 2 )] +[node name="date_picker" parent="stage/controls" index="2" instance=ExtResource( 2 )] anchor_right = 0.0 anchor_bottom = 0.0 margin_top = 106.0 @@ -110,7 +102,7 @@ margin_right = 1080.0 margin_bottom = 306.0 rect_min_size = Vector2( 400, 200 ) -[node name="place" type="LineEdit" parent="stage/controls"] +[node name="place" type="LineEdit" parent="stage/controls" index="3"] margin_top = 310.0 margin_right = 1080.0 margin_bottom = 359.0 @@ -124,7 +116,7 @@ anchor_right = 1.0 anchor_bottom = 1.0 text = "X" -[node name="anesthesia" type="LineEdit" parent="stage/controls"] +[node name="anesthesia" type="LineEdit" parent="stage/controls" index="4"] margin_top = 363.0 margin_right = 1080.0 margin_bottom = 412.0 @@ -132,7 +124,7 @@ placeholder_text = "Anestesia" caret_blink = true caret_blink_speed = 0.5 -[node name="first_assistant" type="LineEdit" parent="stage/controls"] +[node name="first_assistant" type="LineEdit" parent="stage/controls" index="5"] margin_top = 416.0 margin_right = 1080.0 margin_bottom = 465.0 @@ -140,7 +132,7 @@ placeholder_text = "1º Ajudante" caret_blink = true caret_blink_speed = 0.5 -[node name="type" type="LineEdit" parent="stage/controls"] +[node name="type" type="LineEdit" parent="stage/controls" index="6"] margin_top = 469.0 margin_right = 1080.0 margin_bottom = 518.0 @@ -148,7 +140,7 @@ placeholder_text = "Tipo" caret_blink = true caret_blink_speed = 0.5 -[node name="sub_type" type="LineEdit" parent="stage/controls"] +[node name="sub_type" type="LineEdit" parent="stage/controls" index="7"] margin_top = 522.0 margin_right = 1080.0 margin_bottom = 571.0 @@ -156,7 +148,7 @@ placeholder_text = "Subtipo" caret_blink = true caret_blink_speed = 0.5 -[node name="sub_sub_type" type="LineEdit" parent="stage/controls"] +[node name="sub_sub_type" type="LineEdit" parent="stage/controls" index="8"] margin_top = 575.0 margin_right = 1080.0 margin_bottom = 624.0 @@ -164,7 +156,7 @@ placeholder_text = "Sub-Subtipo" caret_blink = true caret_blink_speed = 0.5 -[node name="pathology" type="LineEdit" parent="stage/controls"] +[node name="pathology" type="LineEdit" parent="stage/controls" index="9"] margin_top = 628.0 margin_right = 1080.0 margin_bottom = 677.0 @@ -172,7 +164,7 @@ placeholder_text = "Patologia" caret_blink = true caret_blink_speed = 0.5 -[node name="intervention" type="LineEdit" parent="stage/controls"] +[node name="intervention" type="LineEdit" parent="stage/controls" index="10"] margin_top = 681.0 margin_right = 1080.0 margin_bottom = 730.0 @@ -180,13 +172,13 @@ placeholder_text = "Intervenção" caret_blink = true caret_blink_speed = 0.5 -[node name="is_urgency" type="CheckBox" parent="stage/controls"] +[node name="is_urgency" type="CheckBox" parent="stage/controls" index="11"] margin_top = 734.0 margin_right = 1080.0 margin_bottom = 781.0 text = "Urgência" -[node name="notes" type="LineEdit" parent="stage/controls"] +[node name="notes" type="LineEdit" parent="stage/controls" index="12"] margin_top = 785.0 margin_right = 1080.0 margin_bottom = 834.0 @@ -194,7 +186,7 @@ placeholder_text = "Notas" caret_blink = true caret_blink_speed = 0.5 -[node name="discard" type="Button" parent="stage/controls"] +[node name="discard" type="Button" parent="stage/controls" index="13"] margin_top = 838.0 margin_right = 1080.0 margin_bottom = 888.0 @@ -206,7 +198,7 @@ __meta__ = { "_edit_use_anchors_": false } -[node name="save" type="Button" parent="stage/controls"] +[node name="save" type="Button" parent="stage/controls" index="14"] margin_top = 892.0 margin_right = 1080.0 margin_bottom = 942.0 @@ -271,3 +263,5 @@ valign = 2 __meta__ = { "_edit_use_anchors_": false } + +[editable path="stage"] diff --git a/project.godot b/project.godot index d630b8d..9067744 100644 --- a/project.godot +++ b/project.godot @@ -29,7 +29,7 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://pointer_input_sensor.gd" }, { -"base": "ScrollContainer", +"base": "TouchVerticalContainer", "class": "Stage", "language": "GDScript", "path": "res://logic/stage.gd" @@ -39,6 +39,11 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://touch_item_list/touch_item_list.gd" }, { +"base": "ScrollContainer", +"class": "TouchVerticalContainer", +"language": "GDScript", +"path": "res://touch_vertical_container/touch_vertical_container.gd" +}, { "base": "Control", "class": "ValuePicker", "language": "GDScript", @@ -51,6 +56,7 @@ _global_script_class_icons={ "PointerInputSensor": "", "Stage": "", "TouchItemList": "", +"TouchVerticalContainer": "", "ValuePicker": "" } diff --git a/readme.md b/readme.md index 6a341e3..ccfbca1 100644 --- a/readme.md +++ b/readme.md @@ -17,7 +17,10 @@ Surgery Log - [x] export database to CSV file; - [x] allow to reset database; - [x] split touch logic from database (create TouchItemList); -- [ ] split touch logic from stage (create TouchVerticalContainer); +- [x] split touch logic from stage (create TouchVerticalContainer); +- [ ] on-press: + - highlight item on lists + - mark auto buttons as pressed - [ ] load/store filters CSV file; - [ ] import/export filters to CSV file; - [ ] Implement file access permission check on Android: diff --git a/touch_item_list/touch_item_list.gd b/touch_item_list/touch_item_list.gd index d2d0d37..0534bb2 100644 --- a/touch_item_list/touch_item_list.gd +++ b/touch_item_list/touch_item_list.gd @@ -6,16 +6,16 @@ const POINTER_VELOCITY_DECAYING_FACTOR: float = 2.5 var is_pointer_dragging := false var pointer_drag_velocity := 0.0 -onready var drag_sensor := get_node("drag_sensor") as PointerInputSensor +onready var sensor := get_node("sensor") as PointerInputSensor onready var v_scroll_bar := get_v_scroll() as ScrollBar func _ready(): - drag_sensor.connect("on_press", self, "pointer_input_handler") - drag_sensor.connect("on_drag", self, "pointer_input_handler") - drag_sensor.connect("on_end_drag", self, "pointer_input_handler") - drag_sensor.connect("on_click", self, "pointer_input_handler") - drag_sensor.connect("on_scroll", self, "pointer_input_handler") + sensor.connect("on_press", self, "pointer_input_on_press_handler") + sensor.connect("on_drag", self, "pointer_input_on_drag_handler") + sensor.connect("on_end_drag", self, "pointer_input_on_end_drag_handler") + sensor.connect("on_click", self, "pointer_input_on_click_handler") + sensor.connect("on_scroll", self, "pointer_input_on_scroll_handler") func _process(delta: float): @@ -25,46 +25,48 @@ func _process(delta: float): v_scroll_bar.value -= pointer_drag_velocity * delta -func pointer_input_handler(pointer: PointerInputSensor.PointerInputData): - match pointer.action: - PointerInputSensor.PointerInputAction.ON_PRESS: - is_pointer_dragging = true - - PointerInputSensor.PointerInputAction.ON_DRAG: - is_pointer_dragging = true - pointer_drag_velocity = pointer.velocity.y - v_scroll_bar.value -= pointer.relative_position.y - - PointerInputSensor.PointerInputAction.ON_END_DRAG: - is_pointer_dragging = false - - PointerInputSensor.PointerInputAction.ON_SCROLL: - var target := self - target._gui_input(pointer.event) - - PointerInputSensor.PointerInputAction.ON_CLICK: - var target := self - var position := target.get_global_mouse_position() - target.rect_global_position - - var event_touch := InputEventScreenTouch.new() - event_touch.index = 0 - event_touch.position = position - - var event_mouse := InputEventMouseButton.new() - event_mouse.button_index = BUTTON_LEFT - event_mouse.button_mask = BUTTON_MASK_LEFT - event_mouse.position = position - - event_mouse.pressed = true - event_touch.pressed = true - target._gui_input(event_mouse) - target._gui_input(event_touch) - - target.grab_focus() - - event_mouse.pressed = false - event_touch.pressed = false - target._gui_input(event_mouse) - target._gui_input(event_touch) +func pointer_input_on_press_handler(pointer: PointerInputSensor.PointerInputData): + is_pointer_dragging = true + + +func pointer_input_on_drag_handler(pointer: PointerInputSensor.PointerInputData): + is_pointer_dragging = true + pointer_drag_velocity = pointer.velocity.y + v_scroll_bar.value -= pointer.relative_position.y + + +func pointer_input_on_end_drag_handler(pointer: PointerInputSensor.PointerInputData): + is_pointer_dragging = false + + +func pointer_input_on_click_handler(pointer: PointerInputSensor.PointerInputData): + var target := self + var position := target.get_global_mouse_position() - target.rect_global_position + + var event_touch := InputEventScreenTouch.new() + event_touch.index = 0 + event_touch.position = position + + var event_mouse := InputEventMouseButton.new() + event_mouse.button_index = BUTTON_LEFT + event_mouse.button_mask = BUTTON_MASK_LEFT + event_mouse.position = position + + event_mouse.pressed = true + event_touch.pressed = true + target._gui_input(event_mouse) + target._gui_input(event_touch) + + target.grab_focus() + + event_mouse.pressed = false + event_touch.pressed = false + target._gui_input(event_mouse) + target._gui_input(event_touch) + + +func pointer_input_on_scroll_handler(pointer: PointerInputSensor.PointerInputData): + var target := self + target._gui_input(pointer.event) diff --git a/touch_item_list/touch_item_list.tscn b/touch_item_list/touch_item_list.tscn index 6022ed4..6207e7d 100644 --- a/touch_item_list/touch_item_list.tscn +++ b/touch_item_list/touch_item_list.tscn @@ -13,7 +13,7 @@ __meta__ = { "_edit_use_anchors_": false } -[node name="drag_sensor" type="Control" parent="."] +[node name="sensor" type="Control" parent="."] anchor_right = 1.0 anchor_bottom = 1.0 margin_right = -8.0 diff --git a/touch_vertical_container/touch_vertical_container.gd b/touch_vertical_container/touch_vertical_container.gd new file mode 100644 index 0000000..10462fd --- /dev/null +++ b/touch_vertical_container/touch_vertical_container.gd @@ -0,0 +1,78 @@ +extends ScrollContainer +class_name TouchVerticalContainer + +const POINTER_VELOCITY_DECAYING_FACTOR: float = 2.5 + +var is_pointer_dragging := false +var pointer_drag_velocity := 0.0 +var exclude_controls := [] + + +func _ready(): + + for it in get_node("controls").get_children(): + it = it as Control + if exclude_controls.has(it.name): + continue + + var sensor = PointerInputSensor.new() + it.add_child(sensor) + + sensor.name = "sensor" + sensor.anchor_right = 1.0 + sensor.anchor_bottom = 1.0 + + sensor.connect("on_press", self, "pointer_input_on_press_handler") + sensor.connect("on_drag", self, "pointer_input_on_drag_handler") + sensor.connect("on_end_drag", self, "pointer_input_on_end_drag_handler") + sensor.connect("on_click", self, "pointer_input_on_click_handler") + + it.connect("focus_entered", sensor, "set_mouse_filter", [Control.MOUSE_FILTER_IGNORE]) + it.connect("focus_entered", sensor, "mouse_default_cursor_shape", [Control.CURSOR_IBEAM]) + it.connect("focus_exited", sensor, "set_mouse_filter", [Control.MOUSE_FILTER_STOP]) + it.connect("focus_exited", sensor, "mouse_default_cursor_shape", [Control.CURSOR_ARROW]) + + if it is LineEdit: + it.connect("focus_exited", it, "deselect") + + +func _process(delta: float): + # Apply drag movement inertia. + if is_pointer_dragging == false && abs(pointer_drag_velocity) > 0.5: + pointer_drag_velocity *= clamp((1.0 - POINTER_VELOCITY_DECAYING_FACTOR * delta), 0.0, 1.0) + self.scroll_vertical -= pointer_drag_velocity * delta + + +func pointer_input_on_press_handler(pointer: PointerInputSensor.PointerInputData): + is_pointer_dragging = true + + +func pointer_input_on_drag_handler(pointer: PointerInputSensor.PointerInputData): + is_pointer_dragging = true + pointer_drag_velocity = pointer.velocity.y + self.scroll_vertical -= pointer.relative_position.y + + +func pointer_input_on_end_drag_handler(pointer: PointerInputSensor.PointerInputData): + is_pointer_dragging = false + + +func pointer_input_on_click_handler(pointer: PointerInputSensor.PointerInputData): + var target: Control = pointer.target.get_parent() + var position := target.get_global_mouse_position() + target.grab_focus() + + var button: Button + if target is Button: + button = target + elif target.get_node("auto") is Button: + button = target.get_node("auto") + + if button != null && button.get_global_rect().has_point(position): + if button is CheckBox || button is CheckButton: + button.pressed = !button.pressed + button.emit_signal("button_down") + button.emit_signal("pressed") + button.emit_signal("button_up") + + diff --git a/touch_vertical_container/touch_vertical_container.tscn b/touch_vertical_container/touch_vertical_container.tscn new file mode 100644 index 0000000..770ac38 --- /dev/null +++ b/touch_vertical_container/touch_vertical_container.tscn @@ -0,0 +1,18 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://touch_vertical_container/touch_vertical_container.gd" type="Script" id=1] + +[node name="scroll_container" type="ScrollContainer"] +anchor_right = 1.0 +anchor_bottom = 1.0 +scroll_horizontal_enabled = false +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="controls" type="VBoxContainer" parent="."] +margin_right = 1080.0 +margin_bottom = 1920.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 -- cgit v1.2.3 From 4d8709e47afc2eb8b4e46ced2747b662a849da29 Mon Sep 17 00:00:00 2001 From: dam Date: Fri, 14 Jan 2022 01:11:38 +0000 Subject: Fix PointerInputSensor. Simplify TouchItemList and TouchVerticalContainer. --- logic/database.gd | 1 - logic/stage.gd | 25 +++++++++++++++++ main.gd | 6 ++--- pointer_input_sensor.gd | 2 +- touch_item_list/touch_item_list.gd | 31 ++++++---------------- .../touch_vertical_container.gd | 27 +++---------------- 6 files changed, 41 insertions(+), 51 deletions(-) (limited to 'logic/stage.gd') diff --git a/logic/database.gd b/logic/database.gd index de978b7..3d74da3 100644 --- a/logic/database.gd +++ b/logic/database.gd @@ -151,7 +151,6 @@ func store_database(file_path: String = DATABASE_FILE_PATH): file.open(file_path, File.WRITE) var header := PoolStringArray(DatabaseEntry.ENTRY_PROTOTYPE.keys()) file.store_csv_line(header) - var entry := PoolStringArray() for it in db: # @DAM This approach depends on the order the dictionary fields are created. file.store_csv_line(it.values()) diff --git a/logic/stage.gd b/logic/stage.gd index 12457c9..e4627c2 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -69,6 +69,13 @@ func _ready(): save_button.connect("pressed", self, "save_action") discard_button.connect("pressed", self, "discard_action") + for it in get_node("controls").get_children(): + it = it as Control + if it is LineEdit: + it.connect("focus_entered", it, "set_cursor_position", [99999999]) # @DAM Use MAX_INT + it.connect("focus_exited", it, "deselect") + + var auto_place := place.get_node("auto") as Button auto_place.connect("pressed", self, "auto_populate", ["place"]) @@ -168,3 +175,21 @@ func clear_filters(save_changes: bool = false): store_filters() +func pointer_input_on_click_handler(pointer: PointerInputSensor.PointerInputData): + .pointer_input_on_click_handler(pointer) + + var target: Control = pointer.target.get_parent() + var button: Button + if target is Button: + button = target + elif target.get_node("auto") is Button: + button = target.get_node("auto") + + if button != null && button.get_global_rect().has_point(pointer.current_position): + if button is CheckBox || button is CheckButton: + button.pressed = !button.pressed + button.emit_signal("button_down") + button.emit_signal("pressed") + button.emit_signal("button_up") + + diff --git a/main.gd b/main.gd index c5f7d0d..768366a 100644 --- a/main.gd +++ b/main.gd @@ -26,15 +26,15 @@ func _process(delta: float): if timeout > 0.0: timeout -= delta else: - Engine.target_fps = 10.0 + Engine.target_fps = 10 -func _input(event): +func _input(event: InputEvent): Engine.target_fps = 0 timeout = 3.5 -func _unhandled_input(event): +func _unhandled_input(event: InputEvent): Engine.target_fps = 0 timeout = 3.5 diff --git a/pointer_input_sensor.gd b/pointer_input_sensor.gd index ee5e390..be4d158 100644 --- a/pointer_input_sensor.gd +++ b/pointer_input_sensor.gd @@ -54,7 +54,7 @@ func _ready(): pointer = PointerInputData.new() pointer.target = self connect("mouse_entered", self, "_on_enter_exit", [true]) - connect("mouse_entered", self, "_on_enter_exit", [false]) + connect("mouse_exited", self, "_on_enter_exit", [false]) func _on_enter_exit(is_inside: bool): diff --git a/touch_item_list/touch_item_list.gd b/touch_item_list/touch_item_list.gd index 0534bb2..07d8f30 100644 --- a/touch_item_list/touch_item_list.gd +++ b/touch_item_list/touch_item_list.gd @@ -27,6 +27,7 @@ func _process(delta: float): func pointer_input_on_press_handler(pointer: PointerInputSensor.PointerInputData): is_pointer_dragging = true + grab_focus() func pointer_input_on_drag_handler(pointer: PointerInputSensor.PointerInputData): @@ -40,29 +41,13 @@ func pointer_input_on_end_drag_handler(pointer: PointerInputSensor.PointerInputD func pointer_input_on_click_handler(pointer: PointerInputSensor.PointerInputData): - var target := self - var position := target.get_global_mouse_position() - target.rect_global_position - - var event_touch := InputEventScreenTouch.new() - event_touch.index = 0 - event_touch.position = position - - var event_mouse := InputEventMouseButton.new() - event_mouse.button_index = BUTTON_LEFT - event_mouse.button_mask = BUTTON_MASK_LEFT - event_mouse.position = position - - event_mouse.pressed = true - event_touch.pressed = true - target._gui_input(event_mouse) - target._gui_input(event_touch) - - target.grab_focus() - - event_mouse.pressed = false - event_touch.pressed = false - target._gui_input(event_mouse) - target._gui_input(event_touch) + var selected_idx := get_item_at_position(pointer.current_position - rect_global_position, true) + if selected_idx >= 0: + select(selected_idx) + emit_signal("item_selected", selected_idx) + else: + unselect_all() + emit_signal("nothing_selected") func pointer_input_on_scroll_handler(pointer: PointerInputSensor.PointerInputData): diff --git a/touch_vertical_container/touch_vertical_container.gd b/touch_vertical_container/touch_vertical_container.gd index 10462fd..f9c43e9 100644 --- a/touch_vertical_container/touch_vertical_container.gd +++ b/touch_vertical_container/touch_vertical_container.gd @@ -27,13 +27,8 @@ func _ready(): sensor.connect("on_end_drag", self, "pointer_input_on_end_drag_handler") sensor.connect("on_click", self, "pointer_input_on_click_handler") - it.connect("focus_entered", sensor, "set_mouse_filter", [Control.MOUSE_FILTER_IGNORE]) - it.connect("focus_entered", sensor, "mouse_default_cursor_shape", [Control.CURSOR_IBEAM]) - it.connect("focus_exited", sensor, "set_mouse_filter", [Control.MOUSE_FILTER_STOP]) - it.connect("focus_exited", sensor, "mouse_default_cursor_shape", [Control.CURSOR_ARROW]) - - if it is LineEdit: - it.connect("focus_exited", it, "deselect") + it.connect("focus_entered", sensor, "set_visible", [false]) + it.connect("focus_exited", sensor, "set_visible", [true]) func _process(delta: float): @@ -45,6 +40,7 @@ func _process(delta: float): func pointer_input_on_press_handler(pointer: PointerInputSensor.PointerInputData): is_pointer_dragging = true + grab_focus() func pointer_input_on_drag_handler(pointer: PointerInputSensor.PointerInputData): @@ -58,21 +54,6 @@ func pointer_input_on_end_drag_handler(pointer: PointerInputSensor.PointerInputD func pointer_input_on_click_handler(pointer: PointerInputSensor.PointerInputData): - var target: Control = pointer.target.get_parent() - var position := target.get_global_mouse_position() - target.grab_focus() - - var button: Button - if target is Button: - button = target - elif target.get_node("auto") is Button: - button = target.get_node("auto") - - if button != null && button.get_global_rect().has_point(position): - if button is CheckBox || button is CheckButton: - button.pressed = !button.pressed - button.emit_signal("button_down") - button.emit_signal("pressed") - button.emit_signal("button_up") + pointer.target.get_parent().grab_focus() -- cgit v1.2.3 From e704e2cca56aed71f6e41dd060ddab5babd10569 Mon Sep 17 00:00:00 2001 From: dam Date: Mon, 24 Jan 2022 00:27:59 +0000 Subject: Draft prototype of option sets' structure, load and store. --- logic/database.gd | 1 - logic/stage.gd | 70 +++++++++++++++++++++++++++++++++++++++++++++---------- menu/menu.gd | 26 ++++++++++----------- 3 files changed, 71 insertions(+), 26 deletions(-) (limited to 'logic/stage.gd') diff --git a/logic/database.gd b/logic/database.gd index 3d74da3..b76fda5 100644 --- a/logic/database.gd +++ b/logic/database.gd @@ -125,7 +125,6 @@ func load_database(file_path: String = DATABASE_FILE_PATH): file.open(file_path, File.READ_WRITE) var headers: PoolStringArray var is_first_line := true -# while file.eof_reached() == false: # @DAM Why this? while file.get_position() < file.get_len(): var csv_entry := file.get_csv_line() if is_first_line: diff --git a/logic/stage.gd b/logic/stage.gd index e4627c2..1601522 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -4,10 +4,10 @@ class_name Stage signal save # (database_entry: Dictionary) signal discard # () -const FILTERS_FILE_PATH: String = "user://filters.csv" +const OPTION_SETS_FILE_PATH: String = "user://option_sets.csv" var staged_entry := {} -var filters := { +var option_sets := { "place": { "bloco central": null, "tondela": null, @@ -42,6 +42,33 @@ var filters := { "xpto_28": null, "xpto_29": null, }, + "type": { + "A": { + "sub_type": { + "aA": { + "sub_sub_type": { + "aaA": null, + "aaB": null, + "aaC": null, + } + }, + "aB": { + "sub_sub_type": { + "abA": null, + "abB": null, + "abC": null, + } + }, + }, + }, + "B": { + "sub_type": { + "bA": null, + "bB": null, + }, + }, + "C": null, + }, } onready var process_id := get_node("controls/process_id") as LineEdit @@ -83,12 +110,12 @@ func _ready(): func auto_populate(field: String): var stage_options = get_node("/root/main/popup_list") as Popup stage_options.connect("item_selected", self, "auto_selected", [field], CONNECT_ONESHOT) - stage_options.popup_options(filters[field].keys()) + stage_options.popup_options(option_sets[field].keys()) func auto_selected(index: int, field: String): var field_input: LineEdit = self[field] - field_input.text = filters[field].keys()[index] + field_input.text = option_sets[field].keys()[index] field_input.caret_position = field_input.text.length() @@ -139,12 +166,11 @@ func get_stage() -> Dictionary: return staged_entry -func load_filters(file_path: String = FILTERS_FILE_PATH): +func load_option_sets(file_path: String = OPTION_SETS_FILE_PATH): var file := File.new() file.open(file_path, File.READ_WRITE) var headers: PoolStringArray var is_first_line := true -# while file.eof_reached() == false: # @DAM Why this? while file.get_position() < file.get_len(): var csv_entry := file.get_csv_line() if is_first_line: @@ -162,17 +188,37 @@ func load_filters(file_path: String = FILTERS_FILE_PATH): # entry[field_name] = true if field_value.strip_edges().to_lower() == "true" else false # _: # entry[field_name] = field_value -# filters.append(entry) - - -func store_filters(file_path: String = FILTERS_FILE_PATH): +# option_sets.append(entry) + + +func store_option_sets(file_path: String = OPTION_SETS_FILE_PATH): + var list: Array + var entry := DatabaseEntry.instance_entry() + if option_sets.has("type"): + for type in option_sets["type"].keys(): + entry.type = type + if option_sets["type"][type].has("sub_type"): + for sub_type in option_sets["type"][type]["sub_type"]: + entry.sub_type = sub_type + if option_sets["type"][type]["sub_type"].has("sub_sub_type"): + for sub_sub_type in option_sets["type"][type]["sub_type"][sub_type]["sub_sub_type"].keys(): + entry.sub_sub_type = sub_sub_type + list.append(entry.duplicate()) + else: + pass + else: + pass + + +# if option_sets.has("type"): +# pass # @DAM TODO -func clear_filters(save_changes: bool = false): +func clear_option_sets(save_changes: bool = false): pass # @DAM TODO if save_changes: - store_filters() + store_option_sets() func pointer_input_on_click_handler(pointer: PointerInputSensor.PointerInputData): diff --git a/menu/menu.gd b/menu/menu.gd index b5e1ef2..9b7f99f 100644 --- a/menu/menu.gd +++ b/menu/menu.gd @@ -1,9 +1,9 @@ extends MenuButton const menu_items: Array = [ - { label = "IMPORT FILTERS", action = "_menu_import_filters_action" }, - { label = "EXPORT FILTERS", action = "_menu_export_filters_action" }, - { label = "CLEAR FILTERS", action = "_menu_clear_filters_action" }, + { label = "IMPORT OPTION SETS", action = "_menu_import_option_sets_action" }, + { label = "EXPORT OPTION SETS", action = "_menu_export_option_sets_action" }, + { label = "CLEAR OPTION SETS", action = "_menu_clear_option_sets_action" }, { label = "EXPORT DATA", action = "_menu_export_data_action" }, { label = "CLEAR DATA", action = "_menu_clear_data_action" }, { label = "ABOUT", action = "_menu_about_action" }, @@ -37,28 +37,28 @@ func dialog_cancelled(confirmation_signal_name: String): confirm_action.disconnect(it.signal, it.target, it.method) -func _menu_import_filters_action(): - file_picker.window_title = "IMPORT FILTERS" +func _menu_import_option_sets_action(): + file_picker.window_title = "IMPORT OPTION SETS" file_picker.mode = FileDialog.MODE_OPEN_FILE file_picker.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS) - file_picker.connect("file_selected", stage, "load_filters", [], CONNECT_ONESHOT) + file_picker.connect("file_selected", stage, "load_option_sets", [], CONNECT_ONESHOT) file_picker.show_modal(true) file_picker.invalidate() -func _menu_export_filters_action(): - file_picker.window_title = "EXPORT FILTERS" +func _menu_export_option_sets_action(): + file_picker.window_title = "EXPORT OPTION SETS" file_picker.mode = FileDialog.MODE_SAVE_FILE file_picker.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS) - file_picker.connect("file_selected", stage, "store_filters", [], CONNECT_ONESHOT) + file_picker.connect("file_selected", stage, "store_option_sets", [], CONNECT_ONESHOT) file_picker.show_modal(true) file_picker.invalidate() -func _menu_clear_filters_action(): - confirm_action.window_title = "CLEAR FILTERS" - confirm_action.dialog_text = "Do you want to delete all filters?" - confirm_action.connect("confirmed", stage, "clear_filters", [true], CONNECT_ONESHOT) +func _menu_clear_option_sets_action(): + confirm_action.window_title = "CLEAR OPTION SETS" + confirm_action.dialog_text = "Do you want to delete all option sets?" + confirm_action.connect("confirmed", stage, "clear_option_sets", [true], CONNECT_ONESHOT) confirm_action.show_modal(true) -- cgit v1.2.3 From b35d5de65158ee015a8332f8fd614e2ee5cacf6e Mon Sep 17 00:00:00 2001 From: dam Date: Tue, 25 Jan 2022 23:56:04 +0000 Subject: Draft prototype of option sets structure. --- logic/stage.gd | 40 +++++++++++++++++++++++++++++++++++----- main.gd | 1 - 2 files changed, 35 insertions(+), 6 deletions(-) (limited to 'logic/stage.gd') diff --git a/logic/stage.gd b/logic/stage.gd index 1601522..3c3d59c 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -9,9 +9,9 @@ const OPTION_SETS_FILE_PATH: String = "user://option_sets.csv" var staged_entry := {} var option_sets := { "place": { - "bloco central": null, - "tondela": null, - "xpto_00": null, + "P0": null, + "P1": null, + "P2": null, "xpto_01": null, "xpto_02": null, "xpto_03": null, @@ -42,6 +42,16 @@ var option_sets := { "xpto_28": null, "xpto_29": null, }, + "first_assistant": { + "FA0": null, + "FA1": null, + "FA2": null, + }, + "anesthesia": { + "AN0": null, + "AN1": null, + "AN2": null, + }, "type": { "A": { "sub_type": { @@ -50,14 +60,34 @@ var option_sets := { "aaA": null, "aaB": null, "aaC": null, - } + }, + "pathology": { + "aaP0": null, + "aaP1": null, + "aaP2": null, + }, + "intervention": { + "aaI0": null, + "aaI1": null, + "aaI2": null, + }, }, "aB": { "sub_sub_type": { "abA": null, "abB": null, "abC": null, - } + }, + "pathology": { + "abP0": null, + "abP1": null, + "abP2": null, + }, + "intervention": { + "abI0": null, + "abI1": null, + "abI2": null, + }, }, }, }, diff --git a/main.gd b/main.gd index 768366a..a1b9aca 100644 --- a/main.gd +++ b/main.gd @@ -14,7 +14,6 @@ func _init(): PhysicsServer.set_active(false) - func _process(delta: float): var keyboard_height: int = OS.get_virtual_keyboard_height() for it in controls_sensible_to_keyboard: -- cgit v1.2.3 From 5fefdacf1e69f68b3768f419e3bed826eac79865 Mon Sep 17 00:00:00 2001 From: dam Date: Thu, 3 Feb 2022 01:08:58 +0000 Subject: Implement option sets load/store import/export functions. Setup option set buttons on stage screen. --- logic/database.gd | 1 + logic/stage.gd | 95 +++++++++++++++++++++++++++---------------------------- main.tscn | 68 ++++++++++++++++++++++++++++++++++++++- menu/menu.gd | 3 ++ readme.md | 4 +-- 5 files changed, 120 insertions(+), 51 deletions(-) (limited to 'logic/stage.gd') diff --git a/logic/database.gd b/logic/database.gd index b76fda5..d88b710 100644 --- a/logic/database.gd +++ b/logic/database.gd @@ -143,6 +143,7 @@ func load_database(file_path: String = DATABASE_FILE_PATH): _: entry[field_name] = field_value db.append(entry) + file.close() func store_database(file_path: String = DATABASE_FILE_PATH): diff --git a/logic/stage.gd b/logic/stage.gd index 3c3d59c..4c1521c 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -4,7 +4,7 @@ class_name Stage signal save # (database_entry: Dictionary) signal discard # () -const OPTION_SETS_FILE_PATH: String = "user://option_sets.csv" +const OPTION_SETS_FILE_PATH: String = "user://option_sets.json" var staged_entry := {} var option_sets := { @@ -120,6 +120,8 @@ onready var discard_button := get_node("controls/discard") as Button func _init(): exclude_controls = ["date_picker", "save", "discard"] + load_option_sets() + store_option_sets() # @DAM Only for testing. func _ready(): @@ -132,20 +134,46 @@ func _ready(): it.connect("focus_entered", it, "set_cursor_position", [99999999]) # @DAM Use MAX_INT it.connect("focus_exited", it, "deselect") - - var auto_place := place.get_node("auto") as Button - auto_place.connect("pressed", self, "auto_populate", ["place"]) + # Map option sets buttons. + var option_sets_map := { + "place": place, + "anesthesia": anesthesia, + "first_assistant": first_assistant, + "type": type, + "sub_type": sub_type, + "sub_sub_type": sub_sub_type, + "pathology": pathology, + "intervention": intervention + } + for key in option_sets_map: + var button := option_sets_map[key].get_node("auto") as Button + button.connect("pressed", self, "auto_populate", [key]) + + +func get_option_sets(field: String): + var options: Array + # @DAM WIP Improve match to check if dictionaries being accessed are not null. + match field: + "sub_type": + options = option_sets["type"][type.text][field].keys() + "sub_sub_type", "pathology", "intervention": + options = option_sets["type"][type.text]["sub_type"][sub_type.text][field].keys() + _: + options = option_sets[field].keys() + return options func auto_populate(field: String): var stage_options = get_node("/root/main/popup_list") as Popup stage_options.connect("item_selected", self, "auto_selected", [field], CONNECT_ONESHOT) - stage_options.popup_options(option_sets[field].keys()) + # @DAM WIP Check if we can use directly the output of the get_option_sets(field). + stage_options.popup_options(get_option_sets(field)) func auto_selected(index: int, field: String): var field_input: LineEdit = self[field] - field_input.text = option_sets[field].keys()[index] + # @DAM WIP Check if we can use the index directly on the output of the get_option_sets(field). + field_input.text = get_option_sets(field)[index] field_input.caret_position = field_input.text.length() @@ -199,54 +227,25 @@ func get_stage() -> Dictionary: func load_option_sets(file_path: String = OPTION_SETS_FILE_PATH): var file := File.new() file.open(file_path, File.READ_WRITE) - var headers: PoolStringArray - var is_first_line := true - while file.get_position() < file.get_len(): - var csv_entry := file.get_csv_line() - if is_first_line: - is_first_line = false - headers = csv_entry - continue -# var entry = DatabaseEntry.instance_entry() -# for idx in headers.size(): -# var field_name := headers[idx] -# var field_value := csv_entry[idx] -# match field_name: -# "date_year", "date_month", "date_day": -# entry[field_name] = int(field_value) -# "is_urgency": -# entry[field_name] = true if field_value.strip_edges().to_lower() == "true" else false -# _: -# entry[field_name] = field_value -# option_sets.append(entry) + var file_content = file.get_as_text() + var parse_result = JSON.parse(file_content) + if parse_result.error == OK && typeof(parse_result.result) == TYPE_DICTIONARY: + option_sets = parse_result.result + else: + option_sets = {} + push_error("Failed to parse option sets file: '%s'.") + file.close() func store_option_sets(file_path: String = OPTION_SETS_FILE_PATH): - var list: Array - var entry := DatabaseEntry.instance_entry() - if option_sets.has("type"): - for type in option_sets["type"].keys(): - entry.type = type - if option_sets["type"][type].has("sub_type"): - for sub_type in option_sets["type"][type]["sub_type"]: - entry.sub_type = sub_type - if option_sets["type"][type]["sub_type"].has("sub_sub_type"): - for sub_sub_type in option_sets["type"][type]["sub_type"][sub_type]["sub_sub_type"].keys(): - entry.sub_sub_type = sub_sub_type - list.append(entry.duplicate()) - else: - pass - else: - pass - - -# if option_sets.has("type"): -# - pass # @DAM TODO + var file := File.new() + file.open(file_path, File.WRITE) + file.store_string(JSON.print(option_sets, "" if file_path == OPTION_SETS_FILE_PATH else "\t")) + file.close() func clear_option_sets(save_changes: bool = false): - pass # @DAM TODO + option_sets = {} if save_changes: store_option_sets() diff --git a/main.tscn b/main.tscn index 45cacd4..a6abf1b 100644 --- a/main.tscn +++ b/main.tscn @@ -115,6 +115,9 @@ anchor_left = 0.85 anchor_right = 1.0 anchor_bottom = 1.0 text = "X" +__meta__ = { +"_edit_use_anchors_": false +} [node name="anesthesia" type="LineEdit" parent="stage/controls" index="4"] margin_top = 363.0 @@ -124,6 +127,15 @@ placeholder_text = "Anestesia" caret_blink = true caret_blink_speed = 0.5 +[node name="auto" type="Button" parent="stage/controls/anesthesia"] +anchor_left = 0.85 +anchor_right = 1.0 +anchor_bottom = 1.0 +text = "X" +__meta__ = { +"_edit_use_anchors_": false +} + [node name="first_assistant" type="LineEdit" parent="stage/controls" index="5"] margin_top = 416.0 margin_right = 1080.0 @@ -132,6 +144,15 @@ placeholder_text = "1º Ajudante" caret_blink = true caret_blink_speed = 0.5 +[node name="auto" type="Button" parent="stage/controls/first_assistant"] +anchor_left = 0.85 +anchor_right = 1.0 +anchor_bottom = 1.0 +text = "X" +__meta__ = { +"_edit_use_anchors_": false +} + [node name="type" type="LineEdit" parent="stage/controls" index="6"] margin_top = 469.0 margin_right = 1080.0 @@ -140,6 +161,15 @@ placeholder_text = "Tipo" caret_blink = true caret_blink_speed = 0.5 +[node name="auto" type="Button" parent="stage/controls/type"] +anchor_left = 0.85 +anchor_right = 1.0 +anchor_bottom = 1.0 +text = "X" +__meta__ = { +"_edit_use_anchors_": false +} + [node name="sub_type" type="LineEdit" parent="stage/controls" index="7"] margin_top = 522.0 margin_right = 1080.0 @@ -148,6 +178,15 @@ placeholder_text = "Subtipo" caret_blink = true caret_blink_speed = 0.5 +[node name="auto" type="Button" parent="stage/controls/sub_type"] +anchor_left = 0.85 +anchor_right = 1.0 +anchor_bottom = 1.0 +text = "X" +__meta__ = { +"_edit_use_anchors_": false +} + [node name="sub_sub_type" type="LineEdit" parent="stage/controls" index="8"] margin_top = 575.0 margin_right = 1080.0 @@ -156,6 +195,15 @@ placeholder_text = "Sub-Subtipo" caret_blink = true caret_blink_speed = 0.5 +[node name="auto" type="Button" parent="stage/controls/sub_sub_type"] +anchor_left = 0.85 +anchor_right = 1.0 +anchor_bottom = 1.0 +text = "X" +__meta__ = { +"_edit_use_anchors_": false +} + [node name="pathology" type="LineEdit" parent="stage/controls" index="9"] margin_top = 628.0 margin_right = 1080.0 @@ -164,6 +212,15 @@ placeholder_text = "Patologia" caret_blink = true caret_blink_speed = 0.5 +[node name="auto" type="Button" parent="stage/controls/pathology"] +anchor_left = 0.85 +anchor_right = 1.0 +anchor_bottom = 1.0 +text = "X" +__meta__ = { +"_edit_use_anchors_": false +} + [node name="intervention" type="LineEdit" parent="stage/controls" index="10"] margin_top = 681.0 margin_right = 1080.0 @@ -172,6 +229,15 @@ placeholder_text = "Intervenção" caret_blink = true caret_blink_speed = 0.5 +[node name="auto" type="Button" parent="stage/controls/intervention"] +anchor_left = 0.85 +anchor_right = 1.0 +anchor_bottom = 1.0 +text = "X" +__meta__ = { +"_edit_use_anchors_": false +} + [node name="is_urgency" type="CheckBox" parent="stage/controls" index="11"] margin_top = 734.0 margin_right = 1080.0 @@ -228,7 +294,7 @@ size_flags_vertical = 3 window_title = "PICK FILE" mode_overrides_title = false access = 2 -filters = PoolStringArray( "*.csv" ) +filters = PoolStringArray( "*.*" ) show_hidden_files = true current_dir = "" current_path = "" diff --git a/menu/menu.gd b/menu/menu.gd index 9b7f99f..639a99b 100644 --- a/menu/menu.gd +++ b/menu/menu.gd @@ -41,6 +41,7 @@ func _menu_import_option_sets_action(): file_picker.window_title = "IMPORT OPTION SETS" file_picker.mode = FileDialog.MODE_OPEN_FILE file_picker.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS) + file_picker.filters[0] = "*.json" file_picker.connect("file_selected", stage, "load_option_sets", [], CONNECT_ONESHOT) file_picker.show_modal(true) file_picker.invalidate() @@ -50,6 +51,7 @@ func _menu_export_option_sets_action(): file_picker.window_title = "EXPORT OPTION SETS" file_picker.mode = FileDialog.MODE_SAVE_FILE file_picker.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS) + file_picker.filters[0] = "*.json" file_picker.connect("file_selected", stage, "store_option_sets", [], CONNECT_ONESHOT) file_picker.show_modal(true) file_picker.invalidate() @@ -66,6 +68,7 @@ func _menu_export_data_action(): file_picker.window_title = "EXPORT DATA" file_picker.mode = FileDialog.MODE_SAVE_FILE file_picker.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS) + file_picker.filters[0] = "*.csv" file_picker.connect("file_selected", database, "store_database", [], CONNECT_ONESHOT) file_picker.show_modal(true) file_picker.invalidate() diff --git a/readme.md b/readme.md index d838d5b..476831d 100644 --- a/readme.md +++ b/readme.md @@ -18,8 +18,8 @@ Surgery Log - [x] allow to reset database; - [x] split touch logic from database (create TouchItemList); - [x] split touch logic from stage (create TouchVerticalContainer); -- [ ] load/store filters CSV file; -- [ ] import/export filters to CSV file; +- [x] load/store filters JSON file; +- [x] import/export filters to CSV file; - [ ] add auto-fill buttons on stage screen: - should show a pop-up with multiple options filtered according to current filters; - allow options to be scrolled by dragging; -- cgit v1.2.3 From 9e96569d70660cb18b2d4669e6a71efcb4f863d6 Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 6 Feb 2022 03:19:37 +0000 Subject: WIP: Implement option sets logic. --- logic/database.gd | 3 +- logic/stage.gd | 189 ++++++++++++++++++++++++++----------- readme.md | 7 ++ touch_item_list/touch_item_list.gd | 3 +- 4 files changed, 145 insertions(+), 57 deletions(-) (limited to 'logic/stage.gd') diff --git a/logic/database.gd b/logic/database.gd index d88b710..1b9ffdb 100644 --- a/logic/database.gd +++ b/logic/database.gd @@ -78,7 +78,8 @@ func edit_action(): staged_idx = selected_idx self.visible = false stage.visible = true - stage.set_stage(db[staged_idx]) + var staged := (db[staged_idx] as Dictionary).duplicate(true) + stage.set_stage(staged) func add_action(): diff --git a/logic/stage.gd b/logic/stage.gd index 4c1521c..0c2f97b 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -5,9 +5,22 @@ signal save # (database_entry: Dictionary) signal discard # () const OPTION_SETS_FILE_PATH: String = "user://option_sets.json" +const OPTION_SETS_NOT_AVAILABLE: String = "--" +const OPTION_SETS_TREE_STRUCTURE := { + "place": null, + "anesthesia": null, + "first_assistant": null, + "type": { + "sub_type": { + "sub_sub_type": null, + "pathology": null, + "intervention": null, + } + } +} -var staged_entry := {} var option_sets := { + "thing": {}, "place": { "P0": null, "P1": null, @@ -47,11 +60,11 @@ var option_sets := { "FA1": null, "FA2": null, }, - "anesthesia": { - "AN0": null, - "AN1": null, - "AN2": null, - }, +# "anesthesia": { +# "AN0": null, +# "AN1": null, +# "AN2": null, +# }, "type": { "A": { "sub_type": { @@ -116,12 +129,20 @@ onready var is_urgency := get_node("controls/is_urgency") as Button onready var notes := get_node("controls/notes") as LineEdit onready var save_button := get_node("controls/save") as Button onready var discard_button := get_node("controls/discard") as Button - +onready var option_sets_map := { + "place": place, + "anesthesia": anesthesia, + "first_assistant": first_assistant, + "type": type, + "sub_type": sub_type, + "sub_sub_type": sub_sub_type, + "pathology": pathology, + "intervention": intervention +} func _init(): exclude_controls = ["date_picker", "save", "discard"] load_option_sets() - store_option_sets() # @DAM Only for testing. func _ready(): @@ -135,51 +156,62 @@ func _ready(): it.connect("focus_exited", it, "deselect") # Map option sets buttons. - var option_sets_map := { - "place": place, - "anesthesia": anesthesia, - "first_assistant": first_assistant, - "type": type, - "sub_type": sub_type, - "sub_sub_type": sub_sub_type, - "pathology": pathology, - "intervention": intervention - } + # @DAM TEST defining he option_sets_map onready +# var option_sets_map := { +# "place": place, +# "anesthesia": anesthesia, +# "first_assistant": first_assistant, +# "type": type, +# "sub_type": sub_type, +# "sub_sub_type": sub_sub_type, +# "pathology": pathology, +# "intervention": intervention +# } for key in option_sets_map: var button := option_sets_map[key].get_node("auto") as Button button.connect("pressed", self, "auto_populate", [key]) +func is_sub_dictionary(dictionary: Dictionary, key: String) -> bool: + return dictionary.has(key) && dictionary[key] is Dictionary + + func get_option_sets(field: String): var options: Array - # @DAM WIP Improve match to check if dictionaries being accessed are not null. + match field: "sub_type": - options = option_sets["type"][type.text][field].keys() + if option_sets["type"].get(type.text) != null: + options = option_sets["type"][type.text][field].keys() "sub_sub_type", "pathology", "intervention": - options = option_sets["type"][type.text]["sub_type"][sub_type.text][field].keys() + if option_sets["type"].get(type.text) != null && option_sets["type"][type.text]["sub_type"].get(sub_type.text) != null: + options = option_sets["type"][type.text]["sub_type"][sub_type.text][field].keys() _: options = option_sets[field].keys() + + if options.size() == 0: + options.append(OPTION_SETS_NOT_AVAILABLE) + return options func auto_populate(field: String): var stage_options = get_node("/root/main/popup_list") as Popup stage_options.connect("item_selected", self, "auto_selected", [field], CONNECT_ONESHOT) - # @DAM WIP Check if we can use directly the output of the get_option_sets(field). stage_options.popup_options(get_option_sets(field)) func auto_selected(index: int, field: String): var field_input: LineEdit = self[field] - # @DAM WIP Check if we can use the index directly on the output of the get_option_sets(field). field_input.text = get_option_sets(field)[index] field_input.caret_position = field_input.text.length() func save_action(): self.visible = false - emit_signal("save", get_stage()) + var staged_entry = get_stage() + gather_new_option_sets(staged_entry) + emit_signal("save", staged_entry) func discard_action(): @@ -188,53 +220,100 @@ func discard_action(): func set_stage(entry: Dictionary): - staged_entry = entry.duplicate(true) - process_id.text = staged_entry.process_id - surgery_id.text = staged_entry.surgery_id - date.set_date(staged_entry.date_year, staged_entry.date_month, staged_entry.date_day) - place.text = staged_entry.place - anesthesia.text = staged_entry.anesthesia - first_assistant.text = staged_entry.first_assistant - type.text = staged_entry.type - sub_type.text = staged_entry.sub_type - sub_sub_type.text = staged_entry.sub_sub_type - pathology.text = staged_entry.pathology - intervention.text = staged_entry.intervention - is_urgency.pressed = staged_entry.is_urgency - notes.text = staged_entry.notes + process_id.text = entry.process_id + surgery_id.text = entry.surgery_id + date.set_date(entry.date_year, entry.date_month, entry.date_day) + place.text = entry.place + anesthesia.text = entry.anesthesia + first_assistant.text = entry.first_assistant + type.text = entry.type + sub_type.text = entry.sub_type + sub_sub_type.text = entry.sub_sub_type + pathology.text = entry.pathology + intervention.text = entry.intervention + is_urgency.pressed = entry.is_urgency + notes.text = entry.notes self.scroll_vertical = 0 func get_stage() -> Dictionary: - staged_entry.process_id = process_id.text - staged_entry.surgery_id = surgery_id.text - staged_entry.date_year = date.get_year() - staged_entry.date_month = date.get_month() - staged_entry.date_day = date.get_day() - staged_entry.place = place.text - staged_entry.anesthesia = anesthesia.text - staged_entry.first_assistant= first_assistant.text - staged_entry.type = type.text - staged_entry.sub_type = sub_type.text - staged_entry.sub_sub_type = sub_sub_type.text - staged_entry.pathology = pathology.text - staged_entry.intervention = intervention.text - staged_entry.is_urgency = is_urgency.pressed - staged_entry.notes = notes.text - return staged_entry + var entry := { + "process_id": process_id.text, + "surgery_id": surgery_id.text, + "date_year": date.get_year(), + "date_month": date.get_month(), + "date_day": date.get_day(), + "place": place.text, + "anesthesia": anesthesia.text, + "first_assistant": first_assistant.text, + "type": type.text, + "sub_type": sub_type.text, + "sub_sub_type": sub_sub_type.text, + "pathology": pathology.text, + "intervention": intervention.text, + "is_urgency": is_urgency.pressed, + "notes": notes.text, + } + return entry func load_option_sets(file_path: String = OPTION_SETS_FILE_PATH): var file := File.new() file.open(file_path, File.READ_WRITE) var file_content = file.get_as_text() + file.close() var parse_result = JSON.parse(file_content) if parse_result.error == OK && typeof(parse_result.result) == TYPE_DICTIONARY: option_sets = parse_result.result else: option_sets = {} push_error("Failed to parse option sets file: '%s'.") - file.close() + + # @DAM Only do sanitize_option_sets_dict if file_path is no the default one. + sanitize_option_sets_dict(OPTION_SETS_TREE_STRUCTURE, option_sets) +# if file_path != OPTION_SETS_FILE_PATH: +# sanitize_option_sets() + + +func sanitize_option_sets_dict(blueprint: Dictionary, test: Dictionary): + for key in blueprint: + if test.get(key) == null: + test[key] = {} + if blueprint[key] != null: + for sub_value in test[key]: + if test[key][sub_value] == null: + test[key][sub_value] = {} + sanitize_option_sets_dict(blueprint[key], test[key][sub_value]) + + +func gather_new_option_sets(entry: Dictionary): + pass + # @DAM TODO WIP +# WIP WIP WIP + if entry["type"] != OPTION_SETS_NOT_AVAILABLE: + if option_sets["type"].has(entry["type"]) == false: + +# option_sets["type"][entry["type"]] = {} + + +func sanitize_option_sets(): + for key in OPTION_SETS_TREE_STRUCTURE: + if option_sets.get(key) == null: + option_sets[key] = {} + + for type_key in option_sets["type"]: + var type_value := option_sets["type"][type_key] as Dictionary + if type_value.get("sub_type") == null: + type_value["sub_type"] = {} + else: + for sub_type_key in type_value["sub_type"]: + var sub_type_value := type_value["sub_type"][sub_type_key] as Dictionary + if sub_type_value.get("sub_sub_type") == null: + sub_type_value["sub_sub_type"] = {} + if sub_type_value.get("pathology") == null: + sub_type_value["pathology"] = {} + if sub_type_value.get("intervention") == null: + sub_type_value["intervention"] = {} func store_option_sets(file_path: String = OPTION_SETS_FILE_PATH): diff --git a/readme.md b/readme.md index 476831d..d61fe2d 100644 --- a/readme.md +++ b/readme.md @@ -3,6 +3,8 @@ Surgery Log # Notes - Location of `user://`: `~/.local/share/godot/app_userdata/`. +- null: 16528 +- {}: # ToDo - [x] Datepicker: On click (without drag) reset velocity to 0; show and focus 'input' to allow introducing value. @@ -45,6 +47,11 @@ Surgery Log - on about screen should close it; - on auto-fill pop-up, should close it; - on database screen, should deselect selected item, otherwise should quit the app; +- [ ] main/screen_controller is responsible for whos currently on focus, fade animations, input access (enable/disable), and back_key_notification handling. + For input use: + node.set_process_input(!pause) + node.set_process_unhandled_input(!pause) + node.set_process_unhandled_key_input(!pause) - [ ] add pop-up asking if changes are to be discarded once the stage screen's discard button is pressed; - [ ] add pop-up confirming delete-entry action; (pressing back should cancel the action); - [ ] edit and delete action buttons should be faded-out when no entry is selected; diff --git a/touch_item_list/touch_item_list.gd b/touch_item_list/touch_item_list.gd index 07d8f30..2b18eae 100644 --- a/touch_item_list/touch_item_list.gd +++ b/touch_item_list/touch_item_list.gd @@ -2,6 +2,7 @@ extends ItemList class_name TouchItemList const POINTER_VELOCITY_DECAYING_FACTOR: float = 2.5 +const EXACT_SELECTION: bool = false var is_pointer_dragging := false var pointer_drag_velocity := 0.0 @@ -41,7 +42,7 @@ func pointer_input_on_end_drag_handler(pointer: PointerInputSensor.PointerInputD func pointer_input_on_click_handler(pointer: PointerInputSensor.PointerInputData): - var selected_idx := get_item_at_position(pointer.current_position - rect_global_position, true) + var selected_idx := get_item_at_position(pointer.current_position - rect_global_position, EXACT_SELECTION) if selected_idx >= 0: select(selected_idx) emit_signal("item_selected", selected_idx) -- cgit v1.2.3 From 7931eadbdbf80b4c3b7388c114ff8accb1ea307c Mon Sep 17 00:00:00 2001 From: dam Date: Mon, 7 Feb 2022 01:59:25 +0000 Subject: Implement option sets. --- logic/stage.gd | 233 +++++++++++++++------------------------------------------ menu/menu.gd | 5 +- readme.md | 5 +- 3 files changed, 67 insertions(+), 176 deletions(-) (limited to 'logic/stage.gd') diff --git a/logic/stage.gd b/logic/stage.gd index 0c2f97b..1352816 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -19,100 +19,7 @@ const OPTION_SETS_TREE_STRUCTURE := { } } -var option_sets := { - "thing": {}, - "place": { - "P0": null, - "P1": null, - "P2": null, - "xpto_01": null, - "xpto_02": null, - "xpto_03": null, - "xpto_04": null, - "xpto_05": null, - "xpto_06": null, - "xpto_07": null, - "xpto_08": null, - "xpto_09": null, - "xpto_10": null, - "xpto_11": null, - "xpto_12": null, - "xpto_13": null, - "xpto_14": null, - "xpto_15": null, - "xpto_16": null, - "xpto_17": null, - "xpto_18": null, - "xpto_19": null, - "xpto_20": null, - "xpto_21": null, - "xpto_22": null, - "xpto_23": null, - "xpto_24": null, - "xpto_25": null, - "xpto_26": null, - "xpto_27": null, - "xpto_28": null, - "xpto_29": null, - }, - "first_assistant": { - "FA0": null, - "FA1": null, - "FA2": null, - }, -# "anesthesia": { -# "AN0": null, -# "AN1": null, -# "AN2": null, -# }, - "type": { - "A": { - "sub_type": { - "aA": { - "sub_sub_type": { - "aaA": null, - "aaB": null, - "aaC": null, - }, - "pathology": { - "aaP0": null, - "aaP1": null, - "aaP2": null, - }, - "intervention": { - "aaI0": null, - "aaI1": null, - "aaI2": null, - }, - }, - "aB": { - "sub_sub_type": { - "abA": null, - "abB": null, - "abC": null, - }, - "pathology": { - "abP0": null, - "abP1": null, - "abP2": null, - }, - "intervention": { - "abI0": null, - "abI1": null, - "abI2": null, - }, - }, - }, - }, - "B": { - "sub_type": { - "bA": null, - "bB": null, - }, - }, - "C": null, - }, -} +var option_sets: Dictionary onready var process_id := get_node("controls/process_id") as LineEdit onready var surgery_id := get_node("controls/surgery_id") as LineEdit @@ -129,16 +36,7 @@ onready var is_urgency := get_node("controls/is_urgency") as Button onready var notes := get_node("controls/notes") as LineEdit onready var save_button := get_node("controls/save") as Button onready var discard_button := get_node("controls/discard") as Button -onready var option_sets_map := { - "place": place, - "anesthesia": anesthesia, - "first_assistant": first_assistant, - "type": type, - "sub_type": sub_type, - "sub_sub_type": sub_sub_type, - "pathology": pathology, - "intervention": intervention -} + func _init(): exclude_controls = ["date_picker", "save", "discard"] @@ -156,38 +54,31 @@ func _ready(): it.connect("focus_exited", it, "deselect") # Map option sets buttons. - # @DAM TEST defining he option_sets_map onready -# var option_sets_map := { -# "place": place, -# "anesthesia": anesthesia, -# "first_assistant": first_assistant, -# "type": type, -# "sub_type": sub_type, -# "sub_sub_type": sub_sub_type, -# "pathology": pathology, -# "intervention": intervention -# } + var option_sets_map := { + "place": place, + "anesthesia": anesthesia, + "first_assistant": first_assistant, + "type": type, + "sub_type": sub_type, + "sub_sub_type": sub_sub_type, + "pathology": pathology, + "intervention": intervention + } for key in option_sets_map: var button := option_sets_map[key].get_node("auto") as Button button.connect("pressed", self, "auto_populate", [key]) -func is_sub_dictionary(dictionary: Dictionary, key: String) -> bool: - return dictionary.has(key) && dictionary[key] is Dictionary - - func get_option_sets(field: String): var options: Array match field: "sub_type": - if option_sets["type"].get(type.text) != null: - options = option_sets["type"][type.text][field].keys() + options = option_sets.get("type", {}).get(type.text, {}).get(field, {}).keys() "sub_sub_type", "pathology", "intervention": - if option_sets["type"].get(type.text) != null && option_sets["type"][type.text]["sub_type"].get(sub_type.text) != null: - options = option_sets["type"][type.text]["sub_type"][sub_type.text][field].keys() + options = option_sets.get("type", {}).get(type.text, {}).get("sub_type", {}).get(sub_type.text, {}).get(field, {}).keys() _: - options = option_sets[field].keys() + options = option_sets.get(field, {}).keys() if options.size() == 0: options.append(OPTION_SETS_NOT_AVAILABLE) @@ -209,8 +100,9 @@ func auto_selected(index: int, field: String): func save_action(): self.visible = false - var staged_entry = get_stage() - gather_new_option_sets(staged_entry) + var staged_entry := get_stage() + gather_option_sets(staged_entry) + store_option_sets() emit_signal("save", staged_entry) @@ -257,6 +149,49 @@ func get_stage() -> Dictionary: return entry +func sanitize_option_sets(entry: Dictionary, blueprint: Dictionary = OPTION_SETS_TREE_STRUCTURE): + # Delete extra keys. + var keys_to_delete: Array + for key in entry: + if blueprint.has(key) == false: + keys_to_delete.append(key) + for key in keys_to_delete: + entry.erase(key) + + for key in blueprint: + # Add missing keys. + if typeof(entry.get(key)) != TYPE_DICTIONARY: + entry[key] = {} + # Process sub-keys + if blueprint[key] != null: + for sub_key in entry[key]: + if typeof(entry[key][sub_key]) != TYPE_DICTIONARY: + entry[key][sub_key] = {} + sanitize_option_sets(entry[key][sub_key], blueprint[key]) + + +func gather_option_sets(entry: Dictionary, target: Dictionary = option_sets, blueprint: Dictionary = OPTION_SETS_TREE_STRUCTURE): + for key in blueprint: + if target.get(key) == null: + target[key] = {} + + var value := (entry[key] as String).strip_edges() + if value == "" || value == OPTION_SETS_NOT_AVAILABLE: + continue + + if target[key].has(value) == false: + target[key][value] = null if blueprint[key] == null else {} + + if blueprint[key] != null: + gather_option_sets(entry, target[key][value], blueprint[key]) + + +func import_option_sets(file_path: String = OPTION_SETS_FILE_PATH): + load_option_sets(file_path) + sanitize_option_sets(option_sets) + store_option_sets() + + func load_option_sets(file_path: String = OPTION_SETS_FILE_PATH): var file := File.new() file.open(file_path, File.READ_WRITE) @@ -269,52 +204,6 @@ func load_option_sets(file_path: String = OPTION_SETS_FILE_PATH): option_sets = {} push_error("Failed to parse option sets file: '%s'.") - # @DAM Only do sanitize_option_sets_dict if file_path is no the default one. - sanitize_option_sets_dict(OPTION_SETS_TREE_STRUCTURE, option_sets) -# if file_path != OPTION_SETS_FILE_PATH: -# sanitize_option_sets() - - -func sanitize_option_sets_dict(blueprint: Dictionary, test: Dictionary): - for key in blueprint: - if test.get(key) == null: - test[key] = {} - if blueprint[key] != null: - for sub_value in test[key]: - if test[key][sub_value] == null: - test[key][sub_value] = {} - sanitize_option_sets_dict(blueprint[key], test[key][sub_value]) - - -func gather_new_option_sets(entry: Dictionary): - pass - # @DAM TODO WIP -# WIP WIP WIP - if entry["type"] != OPTION_SETS_NOT_AVAILABLE: - if option_sets["type"].has(entry["type"]) == false: - -# option_sets["type"][entry["type"]] = {} - - -func sanitize_option_sets(): - for key in OPTION_SETS_TREE_STRUCTURE: - if option_sets.get(key) == null: - option_sets[key] = {} - - for type_key in option_sets["type"]: - var type_value := option_sets["type"][type_key] as Dictionary - if type_value.get("sub_type") == null: - type_value["sub_type"] = {} - else: - for sub_type_key in type_value["sub_type"]: - var sub_type_value := type_value["sub_type"][sub_type_key] as Dictionary - if sub_type_value.get("sub_sub_type") == null: - sub_type_value["sub_sub_type"] = {} - if sub_type_value.get("pathology") == null: - sub_type_value["pathology"] = {} - if sub_type_value.get("intervention") == null: - sub_type_value["intervention"] = {} - func store_option_sets(file_path: String = OPTION_SETS_FILE_PATH): var file := File.new() diff --git a/menu/menu.gd b/menu/menu.gd index 639a99b..1df6f28 100644 --- a/menu/menu.gd +++ b/menu/menu.gd @@ -42,7 +42,8 @@ func _menu_import_option_sets_action(): file_picker.mode = FileDialog.MODE_OPEN_FILE file_picker.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS) file_picker.filters[0] = "*.json" - file_picker.connect("file_selected", stage, "load_option_sets", [], CONNECT_ONESHOT) + file_picker.current_file = "" + file_picker.connect("file_selected", stage, "import_option_sets", [true], CONNECT_ONESHOT) file_picker.show_modal(true) file_picker.invalidate() @@ -52,6 +53,7 @@ func _menu_export_option_sets_action(): file_picker.mode = FileDialog.MODE_SAVE_FILE file_picker.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS) file_picker.filters[0] = "*.json" + file_picker.current_file = "" file_picker.connect("file_selected", stage, "store_option_sets", [], CONNECT_ONESHOT) file_picker.show_modal(true) file_picker.invalidate() @@ -69,6 +71,7 @@ func _menu_export_data_action(): file_picker.mode = FileDialog.MODE_SAVE_FILE file_picker.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS) file_picker.filters[0] = "*.csv" + file_picker.current_file = "" file_picker.connect("file_selected", database, "store_database", [], CONNECT_ONESHOT) file_picker.show_modal(true) file_picker.invalidate() diff --git a/readme.md b/readme.md index d61fe2d..51d0eff 100644 --- a/readme.md +++ b/readme.md @@ -3,8 +3,6 @@ Surgery Log # Notes - Location of `user://`: `~/.local/share/godot/app_userdata/`. -- null: 16528 -- {}: # ToDo - [x] Datepicker: On click (without drag) reset velocity to 0; show and focus 'input' to allow introducing value. @@ -22,10 +20,11 @@ Surgery Log - [x] split touch logic from stage (create TouchVerticalContainer); - [x] load/store filters JSON file; - [x] import/export filters to CSV file; -- [ ] add auto-fill buttons on stage screen: +- [x] add option_sets buttons on stage screen: - should show a pop-up with multiple options filtered according to current filters; - allow options to be scrolled by dragging; - selecting option puts that text on the associated LineEdit; +- [ ] check if import_option_sets, store_option_sets, store_database require the parameter save_changes; this requires changes on databse, stage and menu scripts; - [ ] Implement file access permission check on Android: ```py if OS.get_name() == "Android": -- cgit v1.2.3 From cc3f6e5ea29bfe006576a35b5fa24d029a07cf7c Mon Sep 17 00:00:00 2001 From: dam Date: Wed, 9 Feb 2022 00:21:59 +0000 Subject: Add confirmation dialog to entry destructive actions. --- logic/database.gd | 8 ++++++++ logic/stage.gd | 27 ++++++++++++++++++++------- main.gd | 32 +++++++++++++++++++++----------- main.tscn | 18 +++++++++--------- menu/menu.gd | 10 +--------- readme.md | 4 ++-- 6 files changed, 61 insertions(+), 38 deletions(-) (limited to 'logic/stage.gd') diff --git a/logic/database.gd b/logic/database.gd index 3c2d6b7..1518490 100644 --- a/logic/database.gd +++ b/logic/database.gd @@ -7,6 +7,7 @@ var db: Array var selected_idx: int var staged_idx: int +onready var confirm_action := get_node("/root/main/confirm_action") as ConfirmationDialog onready var stage := get_node("/root/main/stage") as Stage onready var delete_button := get_node("actions/delete") as Button onready var edit_button := get_node("actions/edit") as Button @@ -65,6 +66,13 @@ func delete_action(): if selected_idx < 0: return + confirm_action.window_title = "DELETE ENTRY" + confirm_action.dialog_text = "Do you want to delete entry with process ID '%s' from the database?" % db[selected_idx].process_id + confirm_action.connect("confirmed", self, "delete_action_confirmed", [], CONNECT_ONESHOT) + confirm_action.show_modal(true) + + +func delete_action_confirmed(): db.remove(selected_idx) self.remove_item(selected_idx) selected_idx = -1 diff --git a/logic/stage.gd b/logic/stage.gd index 1352816..6f46489 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -19,8 +19,10 @@ const OPTION_SETS_TREE_STRUCTURE := { } } +var staged_entry_hash: int var option_sets: Dictionary +onready var confirm_action := get_node("/root/main/confirm_action") as ConfirmationDialog onready var process_id := get_node("controls/process_id") as LineEdit onready var surgery_id := get_node("controls/surgery_id") as LineEdit onready var date := get_node("controls/date_picker") as DatePicker @@ -65,8 +67,8 @@ func _ready(): "intervention": intervention } for key in option_sets_map: - var button := option_sets_map[key].get_node("auto") as Button - button.connect("pressed", self, "auto_populate", [key]) + var button := option_sets_map[key].get_node("option_set") as Button + button.connect("pressed", self, "show_option_sets", [key]) func get_option_sets(field: String): @@ -86,13 +88,13 @@ func get_option_sets(field: String): return options -func auto_populate(field: String): +func show_option_sets(field: String): var stage_options = get_node("/root/main/popup_list") as Popup - stage_options.connect("item_selected", self, "auto_selected", [field], CONNECT_ONESHOT) + stage_options.connect("item_selected", self, "option_set_selected", [field], CONNECT_ONESHOT) stage_options.popup_options(get_option_sets(field)) -func auto_selected(index: int, field: String): +func option_set_selected(index: int, field: String): var field_input: LineEdit = self[field] field_input.text = get_option_sets(field)[index] field_input.caret_position = field_input.text.length() @@ -107,11 +109,22 @@ func save_action(): func discard_action(): + if get_stage().hash() != staged_entry_hash: + confirm_action.window_title = "DISCARD ENTRY" + confirm_action.dialog_text = "Do you want to discard the changes made?" + confirm_action.connect("confirmed", self, "discard_action_confirmed", [], CONNECT_ONESHOT) + confirm_action.show_modal(true) + else: + discard_action_confirmed() + + +func discard_action_confirmed(): self.visible = false emit_signal("discard") func set_stage(entry: Dictionary): + staged_entry_hash = entry.hash() process_id.text = entry.process_id surgery_id.text = entry.surgery_id date.set_date(entry.date_year, entry.date_month, entry.date_day) @@ -225,8 +238,8 @@ func pointer_input_on_click_handler(pointer: PointerInputSensor.PointerInputData var button: Button if target is Button: button = target - elif target.get_node("auto") is Button: - button = target.get_node("auto") + elif target.get_node("option_set") is Button: + button = target.get_node("option_set") if button != null && button.get_global_rect().has_point(pointer.current_position): if button is CheckBox || button is CheckButton: diff --git a/main.gd b/main.gd index a1b9aca..bf31aa0 100644 --- a/main.gd +++ b/main.gd @@ -1,11 +1,13 @@ extends Control -var timeout: float +var power_throttle_timeout: float -onready var controls_sensible_to_keyboard: Array = [ +onready var file_picker := get_node("/root/main/file_picker") as FileDialog +onready var confirm_action := get_node("/root/main/confirm_action") as ConfirmationDialog +onready var controls_sensible_to_keyboard := [ self, - get_node("/root/main/file_picker"), - get_node("/root/main/confirm_action"), + file_picker, + confirm_action, ] @@ -14,27 +16,35 @@ func _init(): PhysicsServer.set_active(false) +func _ready(): + confirm_action.get_cancel().connect("pressed", self, "dialog_cancelled", ["confirmed"]) + file_picker.get_cancel().connect("pressed", self, "dialog_cancelled", ["file_selected"]) + + func _process(delta: float): var keyboard_height: int = OS.get_virtual_keyboard_height() for it in controls_sensible_to_keyboard: it.margin_bottom = -keyboard_height - # @DAM Debug information. -# $debug.text = "%s" % Engine.get_frames_per_second() -# $debug.text = "%s" % timeout - if timeout > 0.0: - timeout -= delta + if power_throttle_timeout > 0.0: + power_throttle_timeout -= delta else: Engine.target_fps = 10 func _input(event: InputEvent): Engine.target_fps = 0 - timeout = 3.5 + power_throttle_timeout = 3.5 func _unhandled_input(event: InputEvent): Engine.target_fps = 0 - timeout = 3.5 + power_throttle_timeout = 3.5 + + +func dialog_cancelled(confirmation_signal_name: String): + var confirmation_handlers = confirm_action.get_signal_connection_list(confirmation_signal_name) + for it in confirmation_handlers: + confirm_action.disconnect(it.signal, it.target, it.method) diff --git a/main.tscn b/main.tscn index a6abf1b..ead511e 100644 --- a/main.tscn +++ b/main.tscn @@ -110,7 +110,7 @@ placeholder_text = "Local" caret_blink = true caret_blink_speed = 0.5 -[node name="auto" type="Button" parent="stage/controls/place"] +[node name="option_set" type="Button" parent="stage/controls/place"] anchor_left = 0.85 anchor_right = 1.0 anchor_bottom = 1.0 @@ -127,7 +127,7 @@ placeholder_text = "Anestesia" caret_blink = true caret_blink_speed = 0.5 -[node name="auto" type="Button" parent="stage/controls/anesthesia"] +[node name="option_set" type="Button" parent="stage/controls/anesthesia"] anchor_left = 0.85 anchor_right = 1.0 anchor_bottom = 1.0 @@ -144,7 +144,7 @@ placeholder_text = "1º Ajudante" caret_blink = true caret_blink_speed = 0.5 -[node name="auto" type="Button" parent="stage/controls/first_assistant"] +[node name="option_set" type="Button" parent="stage/controls/first_assistant"] anchor_left = 0.85 anchor_right = 1.0 anchor_bottom = 1.0 @@ -161,7 +161,7 @@ placeholder_text = "Tipo" caret_blink = true caret_blink_speed = 0.5 -[node name="auto" type="Button" parent="stage/controls/type"] +[node name="option_set" type="Button" parent="stage/controls/type"] anchor_left = 0.85 anchor_right = 1.0 anchor_bottom = 1.0 @@ -178,7 +178,7 @@ placeholder_text = "Subtipo" caret_blink = true caret_blink_speed = 0.5 -[node name="auto" type="Button" parent="stage/controls/sub_type"] +[node name="option_set" type="Button" parent="stage/controls/sub_type"] anchor_left = 0.85 anchor_right = 1.0 anchor_bottom = 1.0 @@ -195,7 +195,7 @@ placeholder_text = "Sub-Subtipo" caret_blink = true caret_blink_speed = 0.5 -[node name="auto" type="Button" parent="stage/controls/sub_sub_type"] +[node name="option_set" type="Button" parent="stage/controls/sub_sub_type"] anchor_left = 0.85 anchor_right = 1.0 anchor_bottom = 1.0 @@ -212,7 +212,7 @@ placeholder_text = "Patologia" caret_blink = true caret_blink_speed = 0.5 -[node name="auto" type="Button" parent="stage/controls/pathology"] +[node name="option_set" type="Button" parent="stage/controls/pathology"] anchor_left = 0.85 anchor_right = 1.0 anchor_bottom = 1.0 @@ -229,7 +229,7 @@ placeholder_text = "Intervenção" caret_blink = true caret_blink_speed = 0.5 -[node name="auto" type="Button" parent="stage/controls/intervention"] +[node name="option_set" type="Button" parent="stage/controls/intervention"] anchor_left = 0.85 anchor_right = 1.0 anchor_bottom = 1.0 @@ -308,7 +308,7 @@ anchor_bottom = 1.0 size_flags_horizontal = 3 size_flags_vertical = 3 window_title = "CONFIRM ACTION" -dialog_text = "Are you sure you want to delete all filters?" +dialog_text = "Do you confirm this action?" dialog_autowrap = true [node name="popup_list" type="Popup" parent="."] diff --git a/menu/menu.gd b/menu/menu.gd index 1df6f28..5847d5b 100644 --- a/menu/menu.gd +++ b/menu/menu.gd @@ -23,20 +23,12 @@ func _ready(): for idx in range(menu_items.size()): popup.add_item(menu_items[idx].label, idx) popup.connect("id_pressed", self, "id_pressed") - file_picker.get_cancel().connect("pressed", self, "dialog_cancelled", ["file_selected"]) - confirm_action.get_cancel().connect("pressed", self, "dialog_cancelled", ["confirmed"]) func id_pressed(id: int): self.call_deferred(menu_items[id].action) -func dialog_cancelled(confirmation_signal_name: String): - var confirmation_handlers = confirm_action.get_signal_connection_list(confirmation_signal_name) - for it in confirmation_handlers: - confirm_action.disconnect(it.signal, it.target, it.method) - - func _menu_import_option_sets_action(): file_picker.window_title = "IMPORT OPTION SETS" file_picker.mode = FileDialog.MODE_OPEN_FILE @@ -85,7 +77,7 @@ func _menu_clear_data_action(): func _menu_about_action(): - confirm_action.window_title = "FAKE DB" + confirm_action.window_title = "ABOUT" confirm_action.dialog_text = "About text here!" confirm_action.show_modal(true) diff --git a/readme.md b/readme.md index ad0ec8f..7aed207 100644 --- a/readme.md +++ b/readme.md @@ -24,8 +24,8 @@ Surgery Log - should show a pop-up with multiple options filtered according to current filters; - allow options to be scrolled by dragging; - selecting option puts that text on the associated LineEdit; -- [ ] add pop-up asking if changes are to be discarded once the stage screen's discard button is pressed; -- [ ] add pop-up confirming delete-entry action; (pressing back should cancel the action); +- [x] add pop-up asking if changes are to be discarded once the stage screen's discard button is pressed; +- [x] add pop-up confirming delete-entry action; - [ ] edit and delete action buttons should be faded-out when no entry is selected; - [ ] Implement file access permission check on Android: ```py -- cgit v1.2.3 From 02ad9ef6c1e4523d0a3bcc034408fa6967233449 Mon Sep 17 00:00:00 2001 From: dam Date: Thu, 10 Feb 2022 09:36:57 +0000 Subject: Sort option sets alphabetically. --- logic/database.gd | 3 ++- logic/stage.gd | 1 + readme.md | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) (limited to 'logic/stage.gd') diff --git a/logic/database.gd b/logic/database.gd index 7b05932..e14dba4 100644 --- a/logic/database.gd +++ b/logic/database.gd @@ -18,7 +18,6 @@ func _init(): selected_idx = -1 staged_idx = -1 load_database() - store_database() # @DAM Only for testing. func _ready(): @@ -34,6 +33,8 @@ func _ready(): for it in db: self.add_item(get_entry_view(it)) + + clear_selection() func _notification(what: int): diff --git a/logic/stage.gd b/logic/stage.gd index 6f46489..66d6d6d 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -85,6 +85,7 @@ func get_option_sets(field: String): if options.size() == 0: options.append(OPTION_SETS_NOT_AVAILABLE) + options.sort() return options diff --git a/readme.md b/readme.md index 549bbf4..c9117e9 100644 --- a/readme.md +++ b/readme.md @@ -27,6 +27,7 @@ Surgery Log - [x] add pop-up asking if changes are to be discarded once the stage screen's discard button is pressed; - [x] add pop-up confirming delete-entry action; - [x] edit and delete action buttons should be faded-out when no entry is selected; +- [x] Sort option sets alphabetically; - [ ] Implement file access permission check on Android: ```py if OS.get_name() == "Android": -- cgit v1.2.3 From 48a26128f175047528fcc1c96590f1a7bbc281eb Mon Sep 17 00:00:00 2001 From: dam Date: Thu, 17 Feb 2022 00:30:16 +0000 Subject: Prototype with custom option set control. --- logic/stage.gd | 47 +++---- main.tscn | 151 ++++++--------------- option_set/option_set.gd | 15 ++ option_set/option_set.tscn | 34 +++++ project.godot | 6 + .../touch_vertical_container.gd | 35 ++++- 6 files changed, 146 insertions(+), 142 deletions(-) create mode 100644 option_set/option_set.gd create mode 100644 option_set/option_set.tscn (limited to 'logic/stage.gd') diff --git a/logic/stage.gd b/logic/stage.gd index 66d6d6d..3cc5943 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -26,14 +26,14 @@ onready var confirm_action := get_node("/root/main/confirm_action") as Confirmat onready var process_id := get_node("controls/process_id") as LineEdit onready var surgery_id := get_node("controls/surgery_id") as LineEdit onready var date := get_node("controls/date_picker") as DatePicker -onready var place := get_node("controls/place") as LineEdit -onready var anesthesia := get_node("controls/anesthesia") as LineEdit -onready var first_assistant := get_node("controls/first_assistant") as LineEdit -onready var type := get_node("controls/type") as LineEdit -onready var sub_type := get_node("controls/sub_type") as LineEdit -onready var sub_sub_type := get_node("controls/sub_sub_type") as LineEdit -onready var pathology := get_node("controls/pathology") as LineEdit -onready var intervention := get_node("controls/intervention") as LineEdit +onready var place := get_node("controls/place") as OptionSet +onready var anesthesia := get_node("controls/anesthesia") as OptionSet +onready var first_assistant := get_node("controls/first_assistant") as OptionSet +onready var type := get_node("controls/type") as OptionSet +onready var sub_type := get_node("controls/sub_type") as OptionSet +onready var sub_sub_type := get_node("controls/sub_sub_type") as OptionSet +onready var pathology := get_node("controls/pathology") as OptionSet +onready var intervention := get_node("controls/intervention") as OptionSet onready var is_urgency := get_node("controls/is_urgency") as Button onready var notes := get_node("controls/notes") as LineEdit onready var save_button := get_node("controls/save") as Button @@ -49,8 +49,9 @@ func _ready(): save_button.connect("pressed", self, "save_action") discard_button.connect("pressed", self, "discard_action") + # @DAM Not working because we changed the stage/controls/.. estructure with the new option_sets. + # @DAM We could try to do a recursive approach or, build a stack of next items to process. for it in get_node("controls").get_children(): - it = it as Control if it is LineEdit: it.connect("focus_entered", it, "set_cursor_position", [99999999]) # @DAM Use MAX_INT it.connect("focus_exited", it, "deselect") @@ -67,7 +68,12 @@ func _ready(): "intervention": intervention } for key in option_sets_map: - var button := option_sets_map[key].get_node("option_set") as Button +# var button := option_sets_map[key].get_node("option_set") as Button +# button.connect("pressed", self, "show_option_sets", [key]) + +# if key != "place": # @DAM DEBUG +# continue + var button := option_sets_map[key].get_node("options") as Button button.connect("pressed", self, "show_option_sets", [key]) @@ -89,6 +95,7 @@ func get_option_sets(field: String): return options +# @DAM WIP I suspect that these should go inside the option_set class... func show_option_sets(field: String): var stage_options = get_node("/root/main/popup_list") as Popup stage_options.connect("item_selected", self, "option_set_selected", [field], CONNECT_ONESHOT) @@ -96,7 +103,7 @@ func show_option_sets(field: String): func option_set_selected(index: int, field: String): - var field_input: LineEdit = self[field] + var field_input: LineEdit = self[field].get_node("input") field_input.text = get_option_sets(field)[index] field_input.caret_position = field_input.text.length() @@ -232,21 +239,3 @@ func clear_option_sets(save_changes: bool = false): store_option_sets() -func pointer_input_on_click_handler(pointer: PointerInputSensor.PointerInputData): - .pointer_input_on_click_handler(pointer) - - var target: Control = pointer.target.get_parent() - var button: Button - if target is Button: - button = target - elif target.get_node("option_set") is Button: - button = target.get_node("option_set") - - if button != null && button.get_global_rect().has_point(pointer.current_position): - if button is CheckBox || button is CheckButton: - button.pressed = !button.pressed - button.emit_signal("button_down") - button.emit_signal("pressed") - button.emit_signal("button_up") - - diff --git a/main.tscn b/main.tscn index 6f38c89..db7c7f7 100644 --- a/main.tscn +++ b/main.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=11 format=2] +[gd_scene load_steps=12 format=2] [ext_resource path="res://main.gd" type="Script" id=1] [ext_resource path="res://date_picker/date_picker.tscn" type="PackedScene" id=2] @@ -6,6 +6,7 @@ [ext_resource path="res://menu/menu.gd" type="Script" id=4] [ext_resource path="res://logic/database.gd" type="Script" id=5] [ext_resource path="res://fonts/font_icons.tres" type="DynamicFont" id=6] +[ext_resource path="res://option_set/option_set.tscn" type="PackedScene" id=7] [ext_resource path="res://logic/stage.gd" type="Script" id=9] [ext_resource path="res://logic/popup_list.gd" type="Script" id=10] [ext_resource path="res://touch_item_list/touch_item_list.tscn" type="PackedScene" id=11] @@ -52,6 +53,8 @@ __meta__ = { } [node name="delete" type="Button" parent="database/actions"] +margin_right = 95.0 +margin_bottom = 162.0 focus_mode = 0 custom_fonts/font = ExtResource( 6 ) enabled_focus_mode = 0 @@ -62,8 +65,9 @@ __meta__ = { } [node name="edit" type="Button" parent="database/actions"] -anchor_top = 0.333 -anchor_bottom = 0.333 +margin_top = 166.0 +margin_right = 95.0 +margin_bottom = 328.0 focus_mode = 0 custom_fonts/font = ExtResource( 6 ) enabled_focus_mode = 0 @@ -74,8 +78,9 @@ __meta__ = { } [node name="add" type="Button" parent="database/actions"] -anchor_top = 0.667 -anchor_bottom = 0.667 +margin_top = 332.0 +margin_right = 95.0 +margin_bottom = 494.0 focus_mode = 0 custom_fonts/font = ExtResource( 6 ) enabled_focus_mode = 0 @@ -114,141 +119,69 @@ margin_bottom = 332.0 rect_min_size = Vector2( 400, 200 ) size_flags_horizontal = 4 -[node name="place" type="LineEdit" parent="stage/controls" index="3"] +[node name="place" parent="stage/controls" index="3" instance=ExtResource( 7 )] +anchor_right = 0.0 +anchor_bottom = 0.0 margin_top = 336.0 margin_right = 1080.0 margin_bottom = 398.0 -placeholder_text = "Local" -caret_blink = true -caret_blink_speed = 0.5 - -[node name="option_set" type="Button" parent="stage/controls/place"] -anchor_left = 0.88 -anchor_right = 1.0 -anchor_bottom = 1.0 -text = "▽" -__meta__ = { -"_edit_use_anchors_": false -} +rect_min_size = Vector2( 0, 62 ) -[node name="anesthesia" type="LineEdit" parent="stage/controls" index="4"] +[node name="anesthesia" parent="stage/controls" index="4" instance=ExtResource( 7 )] +anchor_right = 0.0 +anchor_bottom = 0.0 margin_top = 402.0 margin_right = 1080.0 margin_bottom = 464.0 -placeholder_text = "Anestesia" -caret_blink = true -caret_blink_speed = 0.5 - -[node name="option_set" type="Button" parent="stage/controls/anesthesia"] -anchor_left = 0.88 -anchor_right = 1.0 -anchor_bottom = 1.0 -text = "▽" -__meta__ = { -"_edit_use_anchors_": false -} +rect_min_size = Vector2( 0, 62 ) -[node name="first_assistant" type="LineEdit" parent="stage/controls" index="5"] +[node name="first_assistant" parent="stage/controls" index="5" instance=ExtResource( 7 )] +anchor_right = 0.0 +anchor_bottom = 0.0 margin_top = 468.0 margin_right = 1080.0 margin_bottom = 530.0 -placeholder_text = "1º Ajudante" -caret_blink = true -caret_blink_speed = 0.5 - -[node name="option_set" type="Button" parent="stage/controls/first_assistant"] -anchor_left = 0.88 -anchor_right = 1.0 -anchor_bottom = 1.0 -text = "▽" -__meta__ = { -"_edit_use_anchors_": false -} +rect_min_size = Vector2( 0, 62 ) -[node name="type" type="LineEdit" parent="stage/controls" index="6"] +[node name="type" parent="stage/controls" index="6" instance=ExtResource( 7 )] +anchor_right = 0.0 +anchor_bottom = 0.0 margin_top = 534.0 margin_right = 1080.0 margin_bottom = 596.0 -placeholder_text = "Tipo" -caret_blink = true -caret_blink_speed = 0.5 - -[node name="option_set" type="Button" parent="stage/controls/type"] -anchor_left = 0.88 -anchor_right = 1.0 -anchor_bottom = 1.0 -text = "▽" -__meta__ = { -"_edit_use_anchors_": false -} +rect_min_size = Vector2( 0, 62 ) -[node name="sub_type" type="LineEdit" parent="stage/controls" index="7"] +[node name="sub_type" parent="stage/controls" index="7" instance=ExtResource( 7 )] +anchor_right = 0.0 +anchor_bottom = 0.0 margin_top = 600.0 margin_right = 1080.0 margin_bottom = 662.0 -placeholder_text = "Subtipo" -caret_blink = true -caret_blink_speed = 0.5 - -[node name="option_set" type="Button" parent="stage/controls/sub_type"] -anchor_left = 0.88 -anchor_right = 1.0 -anchor_bottom = 1.0 -text = "▽" -__meta__ = { -"_edit_use_anchors_": false -} +rect_min_size = Vector2( 0, 62 ) -[node name="sub_sub_type" type="LineEdit" parent="stage/controls" index="8"] +[node name="sub_sub_type" parent="stage/controls" index="8" instance=ExtResource( 7 )] +anchor_right = 0.0 +anchor_bottom = 0.0 margin_top = 666.0 margin_right = 1080.0 margin_bottom = 728.0 -placeholder_text = "Sub-Subtipo" -caret_blink = true -caret_blink_speed = 0.5 - -[node name="option_set" type="Button" parent="stage/controls/sub_sub_type"] -anchor_left = 0.88 -anchor_right = 1.0 -anchor_bottom = 1.0 -text = "▽" -__meta__ = { -"_edit_use_anchors_": false -} +rect_min_size = Vector2( 0, 62 ) -[node name="pathology" type="LineEdit" parent="stage/controls" index="9"] +[node name="pathology" parent="stage/controls" index="9" instance=ExtResource( 7 )] +anchor_right = 0.0 +anchor_bottom = 0.0 margin_top = 732.0 margin_right = 1080.0 margin_bottom = 794.0 -placeholder_text = "Patologia" -caret_blink = true -caret_blink_speed = 0.5 - -[node name="option_set" type="Button" parent="stage/controls/pathology"] -anchor_left = 0.88 -anchor_right = 1.0 -anchor_bottom = 1.0 -text = "▽" -__meta__ = { -"_edit_use_anchors_": false -} +rect_min_size = Vector2( 0, 62 ) -[node name="intervention" type="LineEdit" parent="stage/controls" index="10"] +[node name="intervention" parent="stage/controls" index="10" instance=ExtResource( 7 )] +anchor_right = 0.0 +anchor_bottom = 0.0 margin_top = 798.0 margin_right = 1080.0 margin_bottom = 860.0 -placeholder_text = "Intervenção" -caret_blink = true -caret_blink_speed = 0.5 - -[node name="option_set" type="Button" parent="stage/controls/intervention"] -anchor_left = 0.88 -anchor_right = 1.0 -anchor_bottom = 1.0 -text = "▽" -__meta__ = { -"_edit_use_anchors_": false -} +rect_min_size = Vector2( 0, 62 ) [node name="is_urgency" type="CheckBox" parent="stage/controls" index="11"] margin_top = 864.0 diff --git a/option_set/option_set.gd b/option_set/option_set.gd new file mode 100644 index 0000000..e9cac3f --- /dev/null +++ b/option_set/option_set.gd @@ -0,0 +1,15 @@ +extends Control +class_name OptionSet + +var text: String setget set_text, get_text + +func set_text(var value: String): + input.text = value + +func get_text() -> String: + return input.text + + +onready var input := get_node("input") as LineEdit + + diff --git a/option_set/option_set.tscn b/option_set/option_set.tscn new file mode 100644 index 0000000..fc2aff5 --- /dev/null +++ b/option_set/option_set.tscn @@ -0,0 +1,34 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://option_set/option_set.gd" type="Script" id=1] + +[node name="option_set" type="Control"] +anchor_right = 1.0 +anchor_bottom = 1.0 +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="input" type="LineEdit" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_right = -100.0 +size_flags_horizontal = 3 +placeholder_text = "option set placeholder" +caret_blink = true +caret_blink_speed = 0.5 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="options" type="Button" parent="."] +anchor_left = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_left = -100.0 +grow_horizontal = 0 +text = "▽" +__meta__ = { +"_edit_use_anchors_": false +} diff --git a/project.godot b/project.godot index 9067744..aed55ea 100644 --- a/project.godot +++ b/project.godot @@ -25,6 +25,11 @@ _global_script_classes=[ { "path": "res://date_picker/date_picker.gd" }, { "base": "Control", +"class": "OptionSet", +"language": "GDScript", +"path": "res://option_set/option_set.gd" +}, { +"base": "Control", "class": "PointerInputSensor", "language": "GDScript", "path": "res://pointer_input_sensor.gd" @@ -53,6 +58,7 @@ _global_script_class_icons={ "Database": "", "DatabaseEntry": "", "DatePicker": "", +"OptionSet": "", "PointerInputSensor": "", "Stage": "", "TouchItemList": "", diff --git a/touch_vertical_container/touch_vertical_container.gd b/touch_vertical_container/touch_vertical_container.gd index f9c43e9..7c2ba84 100644 --- a/touch_vertical_container/touch_vertical_container.gd +++ b/touch_vertical_container/touch_vertical_container.gd @@ -27,8 +27,8 @@ func _ready(): sensor.connect("on_end_drag", self, "pointer_input_on_end_drag_handler") sensor.connect("on_click", self, "pointer_input_on_click_handler") - it.connect("focus_entered", sensor, "set_visible", [false]) - it.connect("focus_exited", sensor, "set_visible", [true]) +# it.connect("focus_entered", sensor, "set_visible", [false]) +# it.connect("focus_exited", sensor, "set_visible", [true]) func _process(delta: float): @@ -54,6 +54,33 @@ func pointer_input_on_end_drag_handler(pointer: PointerInputSensor.PointerInputD func pointer_input_on_click_handler(pointer: PointerInputSensor.PointerInputData): - pointer.target.get_parent().grab_focus() - + propagate_click(pointer.target.get_parent(), pointer) + + +# @DAM Maybe replace this with a stack of next items to process approach. +func propagate_click(control: Control, pointer: PointerInputSensor.PointerInputData) -> bool: + if control is PointerInputSensor || control.mouse_filter == MOUSE_FILTER_IGNORE || control.visible == false: + return false + var click_processed := false + if control.get_global_rect().has_point(pointer.current_position): + var children = control.get_children() + children.invert() # @DAM Use inverted index for loop to avoid invert() operation. + for child in children: + if child is Control: + click_processed = click_processed || propagate_click(child, pointer) + if click_processed == true: + break + + if click_processed == false: + if control is CheckBox || control is CheckButton: + control.pressed = !control.pressed + control.grab_focus() + control.emit_signal("button_down") + control.emit_signal("pressed") + control.emit_signal("button_up") + click_processed = true + pointer.target.visible = false + control.connect("focus_exited", pointer.target, "set_visible", [true]) + + return click_processed -- cgit v1.2.3 From 283c0f2d84420bd02550dd4404b306d427fd58af Mon Sep 17 00:00:00 2001 From: dam Date: Fri, 18 Feb 2022 01:34:43 +0000 Subject: Implemented custom option set control. Fixed option sets text input zone being hidden below the button. --- logic/popup_list.gd | 1 + logic/stage.gd | 31 ++-------- main.tscn | 3 + option_set/option_set.gd | 23 ++++++- option_set/option_set.tscn | 3 +- project.godot | 6 ++ readme.md | 5 +- test_input.gd | 42 ------------- test_input.tscn | 58 ------------------ .../touch_vertical_container.gd | 71 ++++++++++++---------- 10 files changed, 82 insertions(+), 161 deletions(-) delete mode 100644 test_input.gd delete mode 100644 test_input.tscn (limited to 'logic/stage.gd') diff --git a/logic/popup_list.gd b/logic/popup_list.gd index 43bf2a1..7e049fb 100644 --- a/logic/popup_list.gd +++ b/logic/popup_list.gd @@ -1,4 +1,5 @@ extends Popup +class_name PopupList signal item_selected diff --git a/logic/stage.gd b/logic/stage.gd index 3cc5943..892dee4 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -49,11 +49,9 @@ func _ready(): save_button.connect("pressed", self, "save_action") discard_button.connect("pressed", self, "discard_action") - # @DAM Not working because we changed the stage/controls/.. estructure with the new option_sets. - # @DAM We could try to do a recursive approach or, build a stack of next items to process. for it in get_node("controls").get_children(): if it is LineEdit: - it.connect("focus_entered", it, "set_cursor_position", [99999999]) # @DAM Use MAX_INT + it.connect("focus_entered", it, "set", ["caret_position", it.max_length]) it.connect("focus_exited", it, "deselect") # Map option sets buttons. @@ -68,16 +66,12 @@ func _ready(): "intervention": intervention } for key in option_sets_map: -# var button := option_sets_map[key].get_node("option_set") as Button -# button.connect("pressed", self, "show_option_sets", [key]) - -# if key != "place": # @DAM DEBUG -# continue - var button := option_sets_map[key].get_node("options") as Button - button.connect("pressed", self, "show_option_sets", [key]) + var button := option_sets_map[key].get_node("options") as Button # @DAM Maybe rename "options". Also requires rename on option_set. + button.connect("pressed", self, "show_options", [key]) -func get_option_sets(field: String): +func show_options(field: String): + var option_set_field := self[field] as OptionSet var options: Array match field: @@ -92,20 +86,7 @@ func get_option_sets(field: String): options.append(OPTION_SETS_NOT_AVAILABLE) options.sort() - return options - - -# @DAM WIP I suspect that these should go inside the option_set class... -func show_option_sets(field: String): - var stage_options = get_node("/root/main/popup_list") as Popup - stage_options.connect("item_selected", self, "option_set_selected", [field], CONNECT_ONESHOT) - stage_options.popup_options(get_option_sets(field)) - - -func option_set_selected(index: int, field: String): - var field_input: LineEdit = self[field].get_node("input") - field_input.text = get_option_sets(field)[index] - field_input.caret_position = field_input.text.length() + option_set_field.show_options(options) func save_action(): diff --git a/main.tscn b/main.tscn index db7c7f7..5122f4c 100644 --- a/main.tscn +++ b/main.tscn @@ -97,6 +97,7 @@ script = ExtResource( 9 ) [node name="process_id" type="LineEdit" parent="stage/controls" index="0"] margin_right = 1080.0 margin_bottom = 62.0 +max_length = 32 placeholder_text = "Nº Processo" caret_blink = true caret_blink_speed = 0.5 @@ -105,6 +106,7 @@ caret_blink_speed = 0.5 margin_top = 66.0 margin_right = 1080.0 margin_bottom = 128.0 +max_length = 32 placeholder_text = "Nº Cirurgia" caret_blink = true caret_blink_speed = 0.5 @@ -193,6 +195,7 @@ text = "Urgência" margin_top = 928.0 margin_right = 1080.0 margin_bottom = 990.0 +max_length = 4096 placeholder_text = "Notas" caret_blink = true caret_blink_speed = 0.5 diff --git a/option_set/option_set.gd b/option_set/option_set.gd index e9cac3f..62d10d0 100644 --- a/option_set/option_set.gd +++ b/option_set/option_set.gd @@ -1,6 +1,7 @@ extends Control class_name OptionSet +var options: Array var text: String setget set_text, get_text func set_text(var value: String): @@ -9,7 +10,27 @@ func set_text(var value: String): func get_text() -> String: return input.text +onready var input := get_node("input") as LineEdit +onready var button := get_node("options") as Button # @DAM Maybe rename this. Also requires renaming on stage. +onready var popup := get_node("/root/main/popup_list") as PopupList -onready var input := get_node("input") as LineEdit +func _ready(): + assert(popup != null, "OptionSet failed to get 'popup' node.") + + input.connect("focus_entered", input, "set", ["caret_position", input.max_length]) + input.connect("focus_exited", input, "deselect") + + +func show_options(options_array: Array): + options = options_array + popup.connect("item_selected", self, "option_selected", [], CONNECT_ONESHOT) + popup.popup_options(options) + + +func option_selected(index: int): + if index >= 0 && index < options.size(): + input.text = options[index] + input.caret_position = input.max_length + button.release_focus() diff --git a/option_set/option_set.tscn b/option_set/option_set.tscn index fc2aff5..bf15806 100644 --- a/option_set/option_set.tscn +++ b/option_set/option_set.tscn @@ -15,7 +15,8 @@ anchor_right = 1.0 anchor_bottom = 1.0 margin_right = -100.0 size_flags_horizontal = 3 -placeholder_text = "option set placeholder" +text = "placeholder" +max_length = 4096 caret_blink = true caret_blink_speed = 0.5 __meta__ = { diff --git a/project.godot b/project.godot index aed55ea..b414dff 100644 --- a/project.godot +++ b/project.godot @@ -34,6 +34,11 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://pointer_input_sensor.gd" }, { +"base": "Popup", +"class": "PopupList", +"language": "GDScript", +"path": "res://logic/popup_list.gd" +}, { "base": "TouchVerticalContainer", "class": "Stage", "language": "GDScript", @@ -60,6 +65,7 @@ _global_script_class_icons={ "DatePicker": "", "OptionSet": "", "PointerInputSensor": "", +"PopupList": "", "Stage": "", "TouchItemList": "", "TouchVerticalContainer": "", diff --git a/readme.md b/readme.md index 795d37c..2437056 100644 --- a/readme.md +++ b/readme.md @@ -42,9 +42,9 @@ Surgery Log else: has_permissions = true ``` -- [ ] Fix the show option sets buttons; they are drawn over the input fields and hide inserted text; +- [x] Fix the show option sets buttons; they are drawn over the input fields and hide inserted text; +- [ ] Allow to parse option sets from database file; - [ ] Database menu and action buttons are not nice; Improve appearance; -- [ ] Show title of database (Log/Historico) and stage (New entry/Novo registo); - [ ] check if import_option_sets, store_option_sets, store_database require the parameter save_changes; this requires changes on databse, stage and menu scripts; - [ ] fix back button: - on stage screen should show pop-up asking it changes are to be discarded; @@ -52,7 +52,6 @@ Surgery Log - on about screen should close it; - on auto-fill pop-up, should close it; - on database screen, should deselect selected item, otherwise should quit the app; -- [ ] Add title to current window (on top, left of the menu); - [ ] Improve menu appearance; - [ ] Setup two themes: - [ ] theme_light diff --git a/test_input.gd b/test_input.gd deleted file mode 100644 index bae409d..0000000 --- a/test_input.gd +++ /dev/null @@ -1,42 +0,0 @@ -extends ColorRect - -onready var debug: RichTextLabel = get_node("/root/main/debug") - - -func _gui_input(event): -# if name == "a": -# accept_event() -# if name == "b" : #&& (event is InputEventScreenTouch): -# if name == "b": -# get_tree().set_input_as_handled() -# accept_event() -# if name == "b" && event is InputEventScreenTouch && event.is_pressed() == false: -# simulate_click() -# call_deferred("simulate_click") - - if event is InputEventScreenTouch || event is InputEventMouseButton: - debug.text += "%s> %-24s\t:\t%s\n" % [name, event.get_class(), event.is_pressed()] - else: - debug.text += "%s> %s\n" % [name, event.get_class()] - - -func simulate_click(): - var event_touch = InputEventScreenTouch.new() - event_touch.index = 0 - event_touch.position = self.get_global_mouse_position() - - var event_mouse = InputEventMouseButton.new() - event_mouse.button_index = BUTTON_LEFT - event_mouse.button_mask = BUTTON_MASK_LEFT - event_mouse.position = self.get_global_mouse_position() - - self.mouse_filter = Control.MOUSE_FILTER_IGNORE - event_mouse.pressed = true - event_touch.pressed = true -# Input.parse_input_event(event_mouse) - Input.parse_input_event(event_touch) - event_mouse.pressed = false - event_touch.pressed = false -# Input.parse_input_event(event_mouse) - Input.parse_input_event(event_touch) - self.mouse_filter = Control.MOUSE_FILTER_STOP diff --git a/test_input.tscn b/test_input.tscn deleted file mode 100644 index 4b507fe..0000000 --- a/test_input.tscn +++ /dev/null @@ -1,58 +0,0 @@ -[gd_scene load_steps=3 format=2] - -[ext_resource path="res://test_input.gd" type="Script" id=1] -[ext_resource path="res://fonts/font_regular.tres" type="DynamicFont" id=2] - -[node name="main" type="Control"] -anchor_right = 1.0 -anchor_bottom = 1.0 -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="debug" type="RichTextLabel" parent="."] -anchor_right = 1.0 -anchor_bottom = 1.0 -mouse_filter = 2 -custom_fonts/normal_font = ExtResource( 2 ) -scroll_following = true -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="a" type="ColorRect" parent="."] -anchor_right = 0.66 -anchor_bottom = 0.66 -color = Color( 1, 0, 0, 0.196078 ) -script = ExtResource( 1 ) -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="b" type="ColorRect" parent="a"] -anchor_left = 0.5 -anchor_right = 1.502 -anchor_bottom = 1.0 -mouse_filter = 1 -color = Color( 0, 1, 0, 0.196078 ) -script = ExtResource( 1 ) -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="c" type="ColorRect" parent="."] -anchor_top = 0.333 -anchor_right = 0.666 -anchor_bottom = 1.0 -color = Color( 0, 0, 1, 0.196078 ) -script = ExtResource( 1 ) -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="d" type="ColorRect" parent="c"] -anchor_left = 0.5 -anchor_right = 1.502 -anchor_bottom = 1.0 -color = Color( 1, 1, 1, 0.196078 ) -script = ExtResource( 1 ) diff --git a/touch_vertical_container/touch_vertical_container.gd b/touch_vertical_container/touch_vertical_container.gd index 7c2ba84..1a7236c 100644 --- a/touch_vertical_container/touch_vertical_container.gd +++ b/touch_vertical_container/touch_vertical_container.gd @@ -26,9 +26,6 @@ func _ready(): sensor.connect("on_drag", self, "pointer_input_on_drag_handler") sensor.connect("on_end_drag", self, "pointer_input_on_end_drag_handler") sensor.connect("on_click", self, "pointer_input_on_click_handler") - -# it.connect("focus_entered", sensor, "set_visible", [false]) -# it.connect("focus_exited", sensor, "set_visible", [true]) func _process(delta: float): @@ -54,33 +51,45 @@ func pointer_input_on_end_drag_handler(pointer: PointerInputSensor.PointerInputD func pointer_input_on_click_handler(pointer: PointerInputSensor.PointerInputData): - propagate_click(pointer.target.get_parent(), pointer) - - -# @DAM Maybe replace this with a stack of next items to process approach. -func propagate_click(control: Control, pointer: PointerInputSensor.PointerInputData) -> bool: - if control is PointerInputSensor || control.mouse_filter == MOUSE_FILTER_IGNORE || control.visible == false: - return false - var click_processed := false - if control.get_global_rect().has_point(pointer.current_position): - var children = control.get_children() - children.invert() # @DAM Use inverted index for loop to avoid invert() operation. - for child in children: - if child is Control: - click_processed = click_processed || propagate_click(child, pointer) - if click_processed == true: - break - - if click_processed == false: - if control is CheckBox || control is CheckButton: - control.pressed = !control.pressed - control.grab_focus() - control.emit_signal("button_down") - control.emit_signal("pressed") - control.emit_signal("button_up") - click_processed = true - pointer.target.visible = false - control.connect("focus_exited", pointer.target, "set_visible", [true]) + # Get last leaf node. + var root := pointer.target.get_parent() as Control + var leaf := root as Node + while leaf.get_child_count() > 0: + leaf = leaf.get_child(leaf.get_child_count() - 1) - return click_processed + # Navigate backwards from leaf to root until we find a node accepting the input. + var tried_leaf_as_root := false + while leaf != root || tried_leaf_as_root == false: + tried_leaf_as_root = leaf == root # Allow a final iteration cycle when leaf reaches root. + var node := leaf + + if node is PointerInputSensor \ + || node is Control == false \ + || node.mouse_filter == MOUSE_FILTER_IGNORE \ + || node.visible == false \ + || node.get_global_rect().has_point(pointer.current_position) == false: + # Get next node to be processed. + var leaf_index_in_parent := leaf.get_position_in_parent() + var parent := leaf.get_parent() + if leaf_index_in_parent == 0: + leaf = parent + else: + leaf = parent.get_child(leaf_index_in_parent - 1) + # Drill down into the new tree branch. + while leaf.get_child_count() > 0: + leaf = leaf.get_child(leaf.get_child_count() - 1) + continue + + var control: Control = node + if control is CheckBox || control is CheckButton: + control.pressed = !control.pressed + + control.grab_focus() + control.emit_signal("button_down") + control.emit_signal("pressed") + control.emit_signal("button_up") + control.connect("focus_exited", pointer.target, "set_visible", [true], CONNECT_ONESHOT) + pointer.target.visible = false + break + -- cgit v1.2.3 From 9aff9cbc19c44b1b97cacf02dcdd8a54a0f02a76 Mon Sep 17 00:00:00 2001 From: dam Date: Sat, 5 Mar 2022 02:02:31 +0000 Subject: Store database and option sets as JSON. Allow to parse options from CSV database or import from JSON file. --- logic/database.gd | 72 ++++++++++++++++++++++++++++++++++++++++--------------- logic/stage.gd | 41 ++++++++++++++++++------------- menu/menu.gd | 66 +++++++++++++++++++++++++++++++------------------- readme.md | 4 ++-- 4 files changed, 122 insertions(+), 61 deletions(-) (limited to 'logic/stage.gd') diff --git a/logic/database.gd b/logic/database.gd index 2258f55..91328c5 100644 --- a/logic/database.gd +++ b/logic/database.gd @@ -1,7 +1,8 @@ extends TouchItemList class_name Database -const DATABASE_FILE_PATH: String = "user://database.csv" +const DATABASE_FILE_PATH: String = "user://database.json" +const DATABASE_FILE_VERSION: int = 1 var db: Array var selected_idx: int @@ -84,7 +85,7 @@ func delete_action_confirmed(): db.remove(selected_idx) self.remove_item(selected_idx) selected_idx = -1 - store_database() + save_database() clear_selection() @@ -125,7 +126,7 @@ func save_stage(entry: Dictionary): set_item_text(selected_idx, get_entry_view(db[selected_idx])) ensure_current_is_visible() - store_database() + save_database() staged_idx = -1 self.visible = true @@ -141,49 +142,84 @@ func discard_stage(): func load_database(file_path: String = DATABASE_FILE_PATH): var file := File.new() file.open(file_path, File.READ_WRITE) - var headers: PoolStringArray - var is_first_line := true + var file_content = file.get_as_text() + file.close() + var parse_result = JSON.parse(file_content) + + if parse_result.error != OK || typeof(parse_result.result) != TYPE_DICTIONARY: + push_error("Failed to parse database file: '%s'.") + return + + if parse_result.result["version"] != DATABASE_FILE_VERSION: + push_error("Invalid database file version '%s', expected '%s'." % DATABASE_FILE_VERSION) + return + + if typeof(parse_result.result["database"]) != TYPE_ARRAY: + push_error("Failed to load database file contents.") + return + + db = parse_result.result["database"] + + +func save_database(file_path: String = DATABASE_FILE_PATH): + var database_file := { + "version": DATABASE_FILE_VERSION, + "database": db, + } + var indentation_char := "" if file_path == DATABASE_FILE_PATH else "\t" + var file_content := JSON.print(database_file, indentation_char) + + var file := File.new() + file.open(file_path, File.WRITE) + file.store_string(file_content) + file.close() + + +static func import_database(file_path: String) -> Array: + var result: Array + var file := File.new() + file.open(file_path, File.READ_WRITE) + var headers := file.get_csv_line() while file.get_position() < file.get_len(): var csv_entry := file.get_csv_line() - if is_first_line: - is_first_line = false - headers = csv_entry - continue var entry = DatabaseEntry.instance_entry() for idx in headers.size(): var field_name := headers[idx] var field_value := csv_entry[idx] match field_name: + "date": + DatabaseEntry.set_entry_date(entry, field_value) "date_year", "date_month", "date_day": entry[field_name] = int(field_value) "is_urgency": entry[field_name] = true if field_value.strip_edges().to_lower() == "true" else false _: - entry[field_name] = field_value - db.append(entry) + if DatabaseEntry.ENTRY_PROTOTYPE.has(field_name): + entry[field_name] = field_value + if DatabaseEntry.is_valid_entry(entry): + result.append(entry) file.close() + return result -func store_database(file_path: String = DATABASE_FILE_PATH): +static func export_database(file_path: String, database: Array = db): var file := File.new() file.open(file_path, File.WRITE) var header := PoolStringArray(DatabaseEntry.ENTRY_PROTOTYPE.keys()) file.store_csv_line(header) - for it in db: + for it in database: # @DAM This approach depends on the order the dictionary fields are created. file.store_csv_line(it.values()) file.close() -func clear_database(save_changes: bool = false): +func clear_database(): clear_selection() self.clear() db.resize(0) - if save_changes: - store_database() -func fake_database(save_changes: bool = false): +func fake_database(): clear_database() for idx in range(500): var today := OS.get_date(true) @@ -199,7 +235,5 @@ func fake_database(save_changes: bool = false): }) db.append(fake_entry) self.add_item(get_entry_view(fake_entry)) - if save_changes: - store_database() diff --git a/logic/stage.gd b/logic/stage.gd index 892dee4..c0583b0 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -5,6 +5,7 @@ signal save # (database_entry: Dictionary) signal discard # () const OPTION_SETS_FILE_PATH: String = "user://option_sets.json" +const OPTION_SETS_FILE_VERSION: int = 1 const OPTION_SETS_NOT_AVAILABLE: String = "--" const OPTION_SETS_TREE_STRUCTURE := { "place": null, @@ -93,7 +94,7 @@ func save_action(): self.visible = false var staged_entry := get_stage() gather_option_sets(staged_entry) - store_option_sets() + save_option_sets() emit_signal("save", staged_entry) @@ -188,35 +189,43 @@ func gather_option_sets(entry: Dictionary, target: Dictionary = option_sets, blu gather_option_sets(entry, target[key][value], blueprint[key]) -func import_option_sets(file_path: String = OPTION_SETS_FILE_PATH): - load_option_sets(file_path) - sanitize_option_sets(option_sets) - store_option_sets() - - func load_option_sets(file_path: String = OPTION_SETS_FILE_PATH): var file := File.new() file.open(file_path, File.READ_WRITE) var file_content = file.get_as_text() file.close() var parse_result = JSON.parse(file_content) - if parse_result.error == OK && typeof(parse_result.result) == TYPE_DICTIONARY: - option_sets = parse_result.result - else: - option_sets = {} + + if parse_result.error != OK || typeof(parse_result.result) != TYPE_DICTIONARY: push_error("Failed to parse option sets file: '%s'.") + return + + if parse_result.result["version"] != OPTION_SETS_FILE_VERSION: + push_error("Invalid option sets file version '%s', expected '%s'." % OPTION_SETS_FILE_VERSION) + return + + if typeof(parse_result.result["option_sets"]) != TYPE_DICTIONARY: + push_error("Failed to load option sets file contents.") + return + + option_sets = parse_result.result["option_sets"] -func store_option_sets(file_path: String = OPTION_SETS_FILE_PATH): +func save_option_sets(file_path: String = OPTION_SETS_FILE_PATH): + var option_sets_file := { + "version": OPTION_SETS_FILE_VERSION, + "option_sets": option_sets, + } + var indentation_char := "" if file_path == OPTION_SETS_FILE_PATH else "\t" + var file_content := JSON.print(option_sets_file, indentation_char) + var file := File.new() file.open(file_path, File.WRITE) - file.store_string(JSON.print(option_sets, "" if file_path == OPTION_SETS_FILE_PATH else "\t")) + file.store_string(file_content) file.close() -func clear_option_sets(save_changes: bool = false): +func clear_option_sets(): option_sets = {} - if save_changes: - store_option_sets() diff --git a/menu/menu.gd b/menu/menu.gd index 5847d5b..9a5bdbb 100644 --- a/menu/menu.gd +++ b/menu/menu.gd @@ -1,13 +1,13 @@ extends MenuButton const menu_items: Array = [ - { label = "IMPORT OPTION SETS", action = "_menu_import_option_sets_action" }, - { label = "EXPORT OPTION SETS", action = "_menu_export_option_sets_action" }, - { label = "CLEAR OPTION SETS", action = "_menu_clear_option_sets_action" }, - { label = "EXPORT DATA", action = "_menu_export_data_action" }, - { label = "CLEAR DATA", action = "_menu_clear_data_action" }, - { label = "ABOUT", action = "_menu_about_action" }, - { label = "FAKE_DB", action = "_menu_fake_db_action" }, + { label = "IMPORT OPTION SETS", action = "import_option_sets_action" }, + { label = "EXPORT OPTION SETS", action = "export_option_sets_action" }, + { label = "CLEAR OPTION SETS", action = "clear_option_sets_action" }, + { label = "EXPORT DATA", action = "export_data_action" }, + { label = "CLEAR DATA", action = "clear_data_action" }, + { label = "ABOUT", action = "about_action" }, + { label = "TEST_FAKE_DB", action = "test_fake_db_action" }, ] const license_font_b612: String = "res://licenses/font_b612.txt" const license_godot: String = "res://licenses/godot.txt" @@ -29,64 +29,82 @@ func id_pressed(id: int): self.call_deferred(menu_items[id].action) -func _menu_import_option_sets_action(): +func import_option_sets_action(): file_picker.window_title = "IMPORT OPTION SETS" file_picker.mode = FileDialog.MODE_OPEN_FILE file_picker.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS) - file_picker.filters[0] = "*.json" + file_picker.filters = ["*.json", "*.csv"] file_picker.current_file = "" - file_picker.connect("file_selected", stage, "import_option_sets", [true], CONNECT_ONESHOT) + file_picker.connect("file_selected", self, "import_option_sets_action_confirmed", [], CONNECT_ONESHOT) file_picker.show_modal(true) file_picker.invalidate() -func _menu_export_option_sets_action(): +func import_option_sets_action_confirmed(file_path: String): + match file_path.get_extension(): + "json": + stage.load_option_sets(file_path) + stage.sanitize_option_sets(stage.option_sets) + stage.save_option_sets() + + "csv": + var database := Database.import_database(file_path) + for it in database: + stage.gather_option_sets(it) + stage.save_option_sets() + + _: + push_error("Invalid file extension selected to be parsed for option sets: '%s'." % file_path.get_file()) + return + + +func export_option_sets_action(): file_picker.window_title = "EXPORT OPTION SETS" file_picker.mode = FileDialog.MODE_SAVE_FILE file_picker.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS) - file_picker.filters[0] = "*.json" + file_picker.filters = ["*.json"] file_picker.current_file = "" - file_picker.connect("file_selected", stage, "store_option_sets", [], CONNECT_ONESHOT) + file_picker.connect("file_selected", stage, "save_option_sets", [], CONNECT_ONESHOT) file_picker.show_modal(true) file_picker.invalidate() -func _menu_clear_option_sets_action(): +func clear_option_sets_action(): confirm_action.window_title = "CLEAR OPTION SETS" confirm_action.dialog_text = "Do you want to delete all option sets?" - confirm_action.connect("confirmed", stage, "clear_option_sets", [true], CONNECT_ONESHOT) + confirm_action.connect("confirmed", stage, "clear_option_sets", [], CONNECT_ONESHOT) confirm_action.show_modal(true) -func _menu_export_data_action(): +func export_data_action(): file_picker.window_title = "EXPORT DATA" file_picker.mode = FileDialog.MODE_SAVE_FILE file_picker.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS) - file_picker.filters[0] = "*.csv" + file_picker.filters = ["*.csv"] file_picker.current_file = "" - file_picker.connect("file_selected", database, "store_database", [], CONNECT_ONESHOT) + file_picker.connect("file_selected", database, "save_database", [], CONNECT_ONESHOT) file_picker.show_modal(true) file_picker.invalidate() -func _menu_clear_data_action(): +func clear_data_action(): confirm_action.window_title = "CLEAR DATA" confirm_action.dialog_text = "Do you want to delete all entries from the database?" - confirm_action.connect("confirmed", database, "clear_database", [true], CONNECT_ONESHOT) + confirm_action.connect("confirmed", database, "clear_database", [], CONNECT_ONESHOT) confirm_action.show_modal(true) -func _menu_about_action(): +func about_action(): confirm_action.window_title = "ABOUT" confirm_action.dialog_text = "About text here!" confirm_action.show_modal(true) # @DAM Hide this debug method before release. -func _menu_fake_db_action(): - confirm_action.window_title = "FAKE DB" +func test_fake_db_action(): + confirm_action.window_title = "TEST FAKE DB" confirm_action.dialog_text = "Do you want to delete all entries from the database and replace by fake entries?" - confirm_action.connect("confirmed", database, "fake_database", [true], CONNECT_ONESHOT) + confirm_action.connect("confirmed", database, "fake_database", [], CONNECT_ONESHOT) confirm_action.show_modal(true) diff --git a/readme.md b/readme.md index bc01851..371eee6 100644 --- a/readme.md +++ b/readme.md @@ -46,7 +46,8 @@ Surgery Log - [x] The stage control must be set to ignore the mouse, otherwise the touch-sensor conflicts with the built-in scroll; - [x] On database, selecting an entry and removing it will leave the action buttons visible while no entry is selected; - [x] Tweak 'POINTER_VELOCITY_DECAYING_FACTOR' and 'POINTER_VELOCITY_BOOST_FACTOR' on database and stage screens; -- [ ] Allow to parse option sets from database file; +- [x] Allow to parse option sets from database file; +- [x] Check if import_option_sets, store_option_sets, store_database require the parameter save_changes; this requires changes on databse, stage and menu scripts; - [ ] Fix back button: - on stage screen should show pop-up asking it changes are to be discarded; - on file-pickers screen should close them; @@ -55,7 +56,6 @@ Surgery Log - on database screen, should deselect selected item, otherwise should quit the app; - [ ] Hide dialogs title bar (appear in the top with 1 or 2 pixels height); - [ ] Database menu and action buttons are not nice; Improve appearance; -- [ ] Check if import_option_sets, store_option_sets, store_database require the parameter save_changes; this requires changes on databse, stage and menu scripts; - [ ] Improve menu appearance; - [ ] Setup two themes: - [ ] theme_light -- cgit v1.2.3 From 5b578acee611e8f5c2d64e88d1e7edbea6144c3f Mon Sep 17 00:00:00 2001 From: dam Date: Mon, 28 Mar 2022 16:00:03 +0000 Subject: Adjusted margin of stage screen. --- logic/stage.gd | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'logic/stage.gd') diff --git a/logic/stage.gd b/logic/stage.gd index c0583b0..bf311b9 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -39,6 +39,7 @@ onready var is_urgency := get_node("controls/is_urgency") as Button onready var notes := get_node("controls/notes") as LineEdit onready var save_button := get_node("controls/save") as Button onready var discard_button := get_node("controls/discard") as Button +onready var scrollbar := get_v_scrollbar() func _init(): @@ -47,10 +48,11 @@ func _init(): func _ready(): + scrollbar.connect("visibility_changed", self, "adjust_layout") save_button.connect("pressed", self, "save_action") discard_button.connect("pressed", self, "discard_action") - for it in get_node("controls").get_children(): + for it in controls.get_children(): if it is LineEdit: it.connect("focus_entered", it, "set", ["caret_position", it.max_length]) it.connect("focus_exited", it, "deselect") @@ -71,6 +73,14 @@ func _ready(): button.connect("pressed", self, "show_options", [key]) +func adjust_layout(): + var margin_size := scrollbar.rect_size.x + self.margin_left = margin_size + self.margin_top = margin_size + self.margin_bottom = -margin_size + self.margin_right = 0.0 if scrollbar.visible else -margin_size + + func show_options(field: String): var option_set_field := self[field] as OptionSet var options: Array @@ -100,7 +110,6 @@ func save_action(): func discard_action(): if get_stage().hash() != staged_entry_hash: - confirm_action.window_title = "DISCARD ENTRY" confirm_action.dialog_text = "Do you want to discard the changes made?" confirm_action.connect("confirmed", self, "discard_action_confirmed", [], CONNECT_ONESHOT) confirm_action.show_modal(true) -- cgit v1.2.3 From 9b619b8c5f117e53b121c2d868b024c7c7d08f4c Mon Sep 17 00:00:00 2001 From: dam Date: Thu, 31 Mar 2022 16:05:54 +0000 Subject: Fixed signals on popup and modal_dialog to make those usable. --- dialog/dialog.gd | 77 +++++++++++++++++++++++++++++++++++++++++++ file_picker.gd | 18 ++++++++++ logic/database.gd | 10 +++--- logic/popup.gd | 36 ++++++++++++++------ logic/stage.gd | 11 ++++--- main.gd | 10 ------ main.tscn | 25 +++++++------- menu/menu.gd | 33 ++++++++++--------- option_set/option_set.gd | 4 --- option_set/option_set_list.gd | 26 +++++++++++---- project.godot | 6 ++++ readme.md | 7 ++-- theme_dark.tres | 6 ++++ theme_white.tres | 6 ---- 14 files changed, 200 insertions(+), 75 deletions(-) create mode 100644 dialog/dialog.gd create mode 100644 file_picker.gd create mode 100644 theme_dark.tres delete mode 100644 theme_white.tres (limited to 'logic/stage.gd') diff --git a/dialog/dialog.gd b/dialog/dialog.gd new file mode 100644 index 0000000..bfdbab5 --- /dev/null +++ b/dialog/dialog.gd @@ -0,0 +1,77 @@ +extends Control +class_name Dialog + +signal answered # (accepted: bool) +signal accepted # () +signal rejected # () + +export var clear_signals_on_hide := true + +var message : Label +var accept : Button +var reject : Button + + +func _init(): + self.anchor_right = 1.0 + self.anchor_bottom = 1.0 + self.rect_clip_content = true + self.connect("hide", self, "_clear_signals") + + reject = Button.new() + reject.text = "reject" + reject.anchor_top = 1.0 + reject.anchor_left = 0.0 + reject.anchor_right = 0.5 + reject.grow_vertical = Control.GROW_DIRECTION_BEGIN + reject.name = "reject" + reject.connect("pressed", self, "_signal_rejected") + add_child(reject) + + accept = Button.new() + accept.text = "accept" + accept.anchor_top = 1.0 + accept.anchor_left = 0.5 + accept.anchor_right = 1.0 + accept.grow_vertical = Control.GROW_DIRECTION_BEGIN + accept.name = "accept" + accept.connect("pressed", self, "_signal_accepted") + add_child(accept) + + message = Label.new() + message.autowrap = true + message.align = Label.ALIGN_CENTER + message.anchor_right = 1.0 + message.anchor_bottom = 1.0 + add_child(message) + + +func _clear_signals(): + if clear_signals_on_hide == false: + return + + for signal_name in ["answered", "accepted", "rejected"]: + for it in get_signal_connection_list(signal_name): + disconnect(it.signal, it.target, it.method) + + +func _signal_rejected(): + emit_signal("rejected") + emit_signal("answered", false) + hide() + + +func _signal_accepted(): + emit_signal("accepted") + emit_signal("answered", true) + hide() + + +func setup(message: String, accept_label: String = "Accept", reject_label: String = "Reject"): + self.message.text = message + accept.visible = accept_label != "" + accept.text = accept_label + reject.visible = reject_label != "" + reject.text = reject_label + + diff --git a/file_picker.gd b/file_picker.gd new file mode 100644 index 0000000..9715d0d --- /dev/null +++ b/file_picker.gd @@ -0,0 +1,18 @@ +extends FileDialog + +export var clear_signals_on_hide := true + + +func _init(): + self.connect("hide", self, "_clear_signals") + + +func _clear_signals(): + if clear_signals_on_hide == false: + return + + for signal_name in ["file_selected"]: + for it in get_signal_connection_list(signal_name): + disconnect(it.signal, it.target, it.method) + + diff --git a/logic/database.gd b/logic/database.gd index 5694dcc..b952356 100644 --- a/logic/database.gd +++ b/logic/database.gd @@ -8,12 +8,12 @@ var db: Array var selected_idx: int var staged_idx: int -onready var confirm_action := get_node("/root/main/confirm_action") as ConfirmationDialog onready var stage := get_node("/root/main/stage") as Stage onready var delete_button := get_node("actions/delete") as Button onready var edit_button := get_node("actions/edit") as Button onready var add_button := get_node("actions/add") as Button - +onready var popup := get_node("/root/main/popup") as ModalPopup +onready var dialog := get_node("/root/main/dialog") as Dialog func _init(): selected_idx = -1 @@ -75,9 +75,9 @@ func delete_action(): if selected_idx < 0: return - confirm_action.dialog_text = "Do you want to delete entry with process ID '%s' from the database?" % db[selected_idx].process_id - confirm_action.connect("confirmed", self, "delete_action_confirmed", [], CONNECT_ONESHOT) - confirm_action.show_modal(true) + dialog.setup("Do you want to delete entry with process ID '%s' from the database?" % db[selected_idx].process_id, "Yes, delete", "No") + dialog.connect("accepted", self, "delete_action_confirmed") + popup.open_popup("Delete entry?", dialog) func delete_action_confirmed(): diff --git a/logic/popup.gd b/logic/popup.gd index 2ff91fe..31897c7 100644 --- a/logic/popup.gd +++ b/logic/popup.gd @@ -3,20 +3,39 @@ class_name ModalPopup signal dismissed # () +export var clear_signals_on_hide := true + var control : Control var control_parent : Node onready var title := get_node("title") as Label onready var background := get_node("background") as Panel +onready var dismiss := get_node("dismiss") as Button func _init(): - anchor_right = 1.0 - anchor_bottom = 1.0 + self.anchor_right = 1.0 + self.anchor_bottom = 1.0 + self.rect_clip_content = true + self.connect("hide", self, "_clear_signals") func _ready(): - get_node("dismiss").connect("pressed", self, "dismiss") + dismiss.connect("pressed", self, "_dismiss") + + +func _clear_signals(): + if clear_signals_on_hide == false: + return + + for signal_name in ["dismissed"]: + for it in get_signal_connection_list(signal_name): + disconnect(it.signal, it.target, it.method) + + +func _dismiss(): + emit_signal("dismissed") + close_popup() func open_popup(title: String, item: Control): @@ -26,6 +45,7 @@ func open_popup(title: String, item: Control): self.title.text = title control = item + control.connect("hide", self, "close_popup") control_parent = control.get_parent() control_parent.remove_child(control) self.add_child(control) @@ -38,23 +58,19 @@ func open_popup(title: String, item: Control): control.margin_top = 20 control.margin_right = -20 control.margin_bottom = -20 - add_child(control) self.show() control.show() -func dismiss(): - emit_signal("dismissed") - - func close_popup(): if visible == false: return - self.hide() + + control.disconnect("hide", self, "close_popup") control.hide() + self.hide() remove_child(control) control_parent.add_child(control) control_parent = null - diff --git a/logic/stage.gd b/logic/stage.gd index bf311b9..fde67d3 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -23,7 +23,7 @@ const OPTION_SETS_TREE_STRUCTURE := { var staged_entry_hash: int var option_sets: Dictionary -onready var confirm_action := get_node("/root/main/confirm_action") as ConfirmationDialog + onready var process_id := get_node("controls/process_id") as LineEdit onready var surgery_id := get_node("controls/surgery_id") as LineEdit onready var date := get_node("controls/date_picker") as DatePicker @@ -40,7 +40,8 @@ onready var notes := get_node("controls/notes") as LineEdit onready var save_button := get_node("controls/save") as Button onready var discard_button := get_node("controls/discard") as Button onready var scrollbar := get_v_scrollbar() - +onready var popup := get_node("/root/main/popup") as ModalPopup +onready var dialog := get_node("/root/main/dialog") as Dialog func _init(): exclude_controls = ["date_picker", "save", "discard"] @@ -110,9 +111,9 @@ func save_action(): func discard_action(): if get_stage().hash() != staged_entry_hash: - confirm_action.dialog_text = "Do you want to discard the changes made?" - confirm_action.connect("confirmed", self, "discard_action_confirmed", [], CONNECT_ONESHOT) - confirm_action.show_modal(true) + dialog.setup("Do you want to discard the changes made?", "Yes, discard", "No") + dialog.connect("accepted", self, "discard_action_confirmed") + popup.open_popup("Discard changes?", dialog) else: discard_action_confirmed() diff --git a/main.gd b/main.gd index 6171409..5612e01 100644 --- a/main.gd +++ b/main.gd @@ -3,11 +3,9 @@ extends Control var power_throttle_timeout: float onready var file_picker := get_node("/root/main/file_picker") as FileDialog -onready var confirm_action := get_node("/root/main/confirm_action") as ConfirmationDialog onready var controls_sensible_to_keyboard := [ self, file_picker, - confirm_action, ] @@ -25,8 +23,6 @@ func _init(): func _ready(): Input.set_use_accumulated_input(false) - confirm_action.get_cancel().connect("pressed", self, "dialog_cancelled", ["confirmed"]) - file_picker.get_cancel().connect("pressed", self, "dialog_cancelled", ["file_selected"]) func _process(delta: float): @@ -50,9 +46,3 @@ func _unhandled_input(event: InputEvent): power_throttle_timeout = 3.5 -func dialog_cancelled(confirmation_signal_name: String): - var confirmation_handlers = confirm_action.get_signal_connection_list(confirmation_signal_name) - for it in confirmation_handlers: - confirm_action.disconnect(it.signal, it.target, it.method) - - diff --git a/main.tscn b/main.tscn index 83bcaf8..d388cb5 100644 --- a/main.tscn +++ b/main.tscn @@ -1,8 +1,8 @@ -[gd_scene load_steps=14 format=2] +[gd_scene load_steps=16 format=2] [ext_resource path="res://main.gd" type="Script" id=1] [ext_resource path="res://date_picker/date_picker.tscn" type="PackedScene" id=2] -[ext_resource path="res://theme_white.tres" type="Theme" id=3] +[ext_resource path="res://theme_dark.tres" type="Theme" id=3] [ext_resource path="res://menu/menu.gd" type="Script" id=4] [ext_resource path="res://logic/database.gd" type="Script" id=5] [ext_resource path="res://fonts/font_icons.tres" type="DynamicFont" id=6] @@ -13,6 +13,8 @@ [ext_resource path="res://touch_item_list/touch_item_list.tscn" type="PackedScene" id=11] [ext_resource path="res://touch_vertical_container/touch_vertical_container.tscn" type="PackedScene" id=12] [ext_resource path="res://fonts/font_mono_regular.tres" type="DynamicFont" id=13] +[ext_resource path="res://dialog/dialog.gd" type="Script" id=14] +[ext_resource path="res://file_picker.gd" type="Script" id=15] [node name="main" type="Control"] anchor_right = 1.0 @@ -240,19 +242,11 @@ filters = PoolStringArray( "*.*" ) show_hidden_files = true current_dir = "" current_path = "" +script = ExtResource( 15 ) __meta__ = { "_edit_use_anchors_": false } -[node name="confirm_action" type="ConfirmationDialog" parent="."] -anchor_right = 1.0 -anchor_bottom = 1.0 -size_flags_horizontal = 3 -size_flags_vertical = 3 -window_title = "" -dialog_text = "Do you confirm this action?" -dialog_autowrap = true - [node name="popup" type="ColorRect" parent="."] visible = false anchor_right = 1.0 @@ -290,7 +284,16 @@ flat = true [node name="option_set_list" type="Control" parent="."] visible = false +anchor_right = 1.0 +anchor_bottom = 1.0 rect_clip_content = true script = ExtResource( 8 ) +[node name="dialog" type="Control" parent="."] +visible = false +anchor_right = 1.0 +anchor_bottom = 1.0 +rect_clip_content = true +script = ExtResource( 14 ) + [editable path="stage"] diff --git a/menu/menu.gd b/menu/menu.gd index c963270..021c005 100644 --- a/menu/menu.gd +++ b/menu/menu.gd @@ -12,8 +12,9 @@ const menu_items: Array = [ const license_font_b612: String = "res://licenses/font_b612.txt" const license_godot: String = "res://licenses/godot.txt" -onready var popup := get_popup() as PopupMenu -onready var confirm_action := get_node("/root/main/confirm_action") as ConfirmationDialog +onready var menu := get_popup() as PopupMenu +onready var popup := get_node("/root/main/popup") as ModalPopup +onready var dialog := get_node("/root/main/dialog") as Dialog onready var file_picker := get_node("/root/main/file_picker") as FileDialog onready var database := get_node("/root/main/database") as Database onready var stage := get_node("/root/main/stage") as Stage @@ -21,8 +22,8 @@ onready var stage := get_node("/root/main/stage") as Stage func _ready(): for idx in range(menu_items.size()): - popup.add_item(menu_items[idx].label, idx) - popup.connect("id_pressed", self, "id_pressed") + menu.add_item(menu_items[idx].label, idx) + menu.connect("id_pressed", self, "id_pressed") func id_pressed(id: int): @@ -34,7 +35,7 @@ func import_option_sets_action(): file_picker.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS) file_picker.filters = ["*.json", "*.csv"] file_picker.current_file = "" - file_picker.connect("file_selected", self, "import_option_sets_action_confirmed", [], CONNECT_ONESHOT) + file_picker.connect("file_selected", self, "import_option_sets_action_confirmed") file_picker.show_modal(true) file_picker.invalidate() @@ -68,9 +69,9 @@ func export_option_sets_action(): func clear_option_sets_action(): - confirm_action.dialog_text = "Do you want to delete all option sets?" - confirm_action.connect("confirmed", stage, "clear_option_sets", [], CONNECT_ONESHOT) - confirm_action.show_modal(true) + dialog.setup("Do you want to delete all option sets?", "Yes, delete.", "No") + dialog.connect("accepted", stage, "clear_option_sets") + popup.open_popup("Clear option sets?", dialog) func export_data_action(): @@ -84,20 +85,20 @@ func export_data_action(): func clear_data_action(): - confirm_action.dialog_text = "Do you want to delete all entries from the database?" - confirm_action.connect("confirmed", database, "clear_database", [], CONNECT_ONESHOT) - confirm_action.show_modal(true) + dialog.setup("Do you want to delete all entries from the database?", "Yes, delete.", "No") + dialog.connect("accepted", database, "clear_database") + popup.open_popup("Clear database?", dialog) func about_action(): - confirm_action.dialog_text = "Surgery Log\nversion 2022-02-27" - confirm_action.show_modal(true) + dialog.setup("Surgery Log\nversion 0.1", "", "") + popup.open_popup("About", dialog) # @DAM Hide this debug method before release. func test_fake_db_action(): - confirm_action.dialog_text = "Do you want to delete all entries from the database and replace by fake entries?" - confirm_action.connect("confirmed", database, "fake_database", [], CONNECT_ONESHOT) - confirm_action.show_modal(true) + dialog.setup("Do you want to delete all entries from the database and replace by fake entries?", "Yes, replace.", "No") + dialog.connect("accepted", database, "fake_database") + popup.open_popup("Fake DB?", dialog) diff --git a/option_set/option_set.gd b/option_set/option_set.gd index 637d033..0e8f90e 100644 --- a/option_set/option_set.gd +++ b/option_set/option_set.gd @@ -35,14 +35,10 @@ func show_options(options_array: Array): options.unselect() options.connect("item_selected", self, "popup_result", []) options.connect("nothing_selected", self, "popup_result", [-1, ""]) - popup.connect("dismissed", self, "popup_result", [selected_idx, input.text]) popup.open_popup(input.placeholder_text, options) func popup_result(index: int, text: String): - options.disconnect("item_selected", self, "popup_result") - options.disconnect("nothing_selected", self, "popup_result") - popup.disconnect("dismissed", self, "popup_result") selected_idx = index input.text = text input.caret_position = input.max_length diff --git a/option_set/option_set_list.gd b/option_set/option_set_list.gd index ea6b2d8..8aad1b1 100644 --- a/option_set/option_set_list.gd +++ b/option_set/option_set_list.gd @@ -5,6 +5,8 @@ const POINTER_VELOCITY_DECAYING_FACTOR: float = PI const POINTER_VELOCITY_BOOST_FACTOR: float = 1.25 const EXACT_SELECTION: bool = false +export var clear_signals_on_hide := true + signal item_selected # (idx: int, text: String) signal nothing_selected # () @@ -17,7 +19,7 @@ var pointer_drag_velocity := 0.0 var when_last_dragged := 0 var labels : Control -var labels_end_positions: Array +var labels_end_positions : Array var labels_sizes : Array var items : Array @@ -35,7 +37,8 @@ func _init(): self.anchor_right = 1.0 self.anchor_bottom = 1.0 self.rect_clip_content = true - + self.connect("hide", self, "_clear_signals") + labels = Control.new() labels.anchor_right = 1.0 labels.anchor_bottom = 1.0 @@ -219,6 +222,7 @@ func get_item_at_position(mouse_position: Vector2) -> int: var position := mouse_position.y + labels_offset var item_idx := labels_end_positions.bsearch(position) + # @DAM Fix this to return -1 if no item was selected. return int(min(item_idx, items.size()-1)) # Return last item when position is below it. @@ -227,6 +231,7 @@ func select(index: int): emit_signal("item_selected", selected_idx, items[selected_idx]) +# @DAM Do we really need this? We should have just one signal to the selection. func unselect(): selected_idx = -1 emit_signal("nothing_selected") @@ -256,10 +261,10 @@ func pointer_input_on_end_drag_handler(pointer: PointerInputSensor.PointerInputD func pointer_input_on_click_handler(pointer: PointerInputSensor.PointerInputData): - var selected_idx := get_item_at_position(pointer.current_position - rect_global_position) - if selected_idx >= 0: - select(selected_idx) - emit_signal("item_selected", selected_idx) + var item_idx := get_item_at_position(pointer.current_position - rect_global_position) + if item_idx >= 0: + select(item_idx) + emit_signal("item_selected", item_idx) else: unselect() emit_signal("nothing_selected") @@ -269,3 +274,12 @@ func pointer_input_on_scroll_handler(pointer: PointerInputSensor.PointerInputDat pointer_drag_velocity -= pointer.scroll * POINTER_VELOCITY_BOOST_FACTOR * screen_dpcm +func _clear_signals(): + if clear_signals_on_hide == false: + return + + for signal_name in ["item_selected", "nothing_selected"]: + for it in get_signal_connection_list(signal_name): + disconnect(it.signal, it.target, it.method) + + diff --git a/project.godot b/project.godot index 4a59943..4a3e1ed 100644 --- a/project.godot +++ b/project.godot @@ -25,6 +25,11 @@ _global_script_classes=[ { "path": "res://date_picker/date_picker.gd" }, { "base": "Control", +"class": "Dialog", +"language": "GDScript", +"path": "res://dialog/dialog.gd" +}, { +"base": "Control", "class": "ModalPopup", "language": "GDScript", "path": "res://logic/popup.gd" @@ -68,6 +73,7 @@ _global_script_class_icons={ "Database": "", "DatabaseEntry": "", "DatePicker": "", +"Dialog": "", "ModalPopup": "", "OptionSet": "", "OptionSetList": "", diff --git a/readme.md b/readme.md index c32faf9..5d48eff 100644 --- a/readme.md +++ b/readme.md @@ -55,8 +55,11 @@ Surgery Log - [x] title; - [x] dismiss button; - [x] Hide dialogs title bar (appear in the top with 1 or 2 pixels height); -- [ ] Use popup to display confirmation messages; -- [ ] What to do when the optionset has no options available? +- [x] Fix automation to automatically disconnect acceptance signal handlers when reject is chosen on file_picker/confirm_action; +- [x] Use popup to display confirmation messages; +- [ ] Improve option sets: + - [ ] selecting outside optionset entry should select none + - [ ] if no options are available, show nothing instead of "--" - [ ] Fix back button: - on stage screen should show pop-up asking it changes are to be discarded; - on file-pickers screen should close them; diff --git a/theme_dark.tres b/theme_dark.tres new file mode 100644 index 0000000..832b226 --- /dev/null +++ b/theme_dark.tres @@ -0,0 +1,6 @@ +[gd_resource type="Theme" load_steps=2 format=2] + +[ext_resource path="res://fonts/font_regular.tres" type="DynamicFont" id=1] + +[resource] +default_font = ExtResource( 1 ) diff --git a/theme_white.tres b/theme_white.tres deleted file mode 100644 index 832b226..0000000 --- a/theme_white.tres +++ /dev/null @@ -1,6 +0,0 @@ -[gd_resource type="Theme" load_steps=2 format=2] - -[ext_resource path="res://fonts/font_regular.tres" type="DynamicFont" id=1] - -[resource] -default_font = ExtResource( 1 ) -- cgit v1.2.3 From 8491abc4805cee3b2dfb74817ef8908df085c5cf Mon Sep 17 00:00:00 2001 From: dam Date: Fri, 1 Apr 2022 11:32:45 +0000 Subject: Option set changed to work with empty list instead of creating dummy -- entry. --- logic/stage.gd | 3 --- option_set/option_set.gd | 15 ++++++--------- option_set/option_set_list.gd | 35 +++++++++++++++++------------------ readme.md | 4 +--- 4 files changed, 24 insertions(+), 33 deletions(-) (limited to 'logic/stage.gd') diff --git a/logic/stage.gd b/logic/stage.gd index fde67d3..ef9ce3d 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -94,9 +94,6 @@ func show_options(field: String): _: options = option_sets.get(field, {}).keys() - if options.size() == 0: - options.append(OPTION_SETS_NOT_AVAILABLE) - options.sort() option_set_field.show_options(options) diff --git a/option_set/option_set.gd b/option_set/option_set.gd index 0e8f90e..bb37736 100644 --- a/option_set/option_set.gd +++ b/option_set/option_set.gd @@ -29,19 +29,16 @@ func _ready(): func show_options(options_array: Array): options.clear_items() options.add_items(options_array) - if options_array[selected_idx] == input.text: - options.select(selected_idx) - else: - options.unselect() - options.connect("item_selected", self, "popup_result", []) - options.connect("nothing_selected", self, "popup_result", [-1, ""]) + options.select(options_array.find(input.text)) + options.connect("selection_changed", self, "popup_result") popup.open_popup(input.placeholder_text, options) func popup_result(index: int, text: String): - selected_idx = index - input.text = text - input.caret_position = input.max_length + if index != -1: + selected_idx = index + input.text = text + input.caret_position = input.max_length popup.close_popup() diff --git a/option_set/option_set_list.gd b/option_set/option_set_list.gd index 8aad1b1..4a8841f 100644 --- a/option_set/option_set_list.gd +++ b/option_set/option_set_list.gd @@ -7,8 +7,7 @@ const EXACT_SELECTION: bool = false export var clear_signals_on_hide := true -signal item_selected # (idx: int, text: String) -signal nothing_selected # () +signal selection_changed # (idx: int, text: String) export var autowrap := true @@ -24,6 +23,7 @@ var labels_sizes : Array var items : Array var selected_idx := -1 +var selected_item := "" var is_dirty := true var normal_style : StyleBoxFlat @@ -78,6 +78,11 @@ func _init(): sensor.connect("on_scroll", self, "pointer_input_on_scroll_handler") +#func _ready(): +# for idx in range(5): +# add_item(">> %d" % idx) + + func mark_as_dirty(): is_dirty = true @@ -119,6 +124,8 @@ func remove_items(indices: Array): func clear_items(): items.clear() + selected_idx = -1 + selected_item = "" v_scroll_bar.value = 0.0 is_dirty = true @@ -215,26 +222,23 @@ func _process(delta): func get_item_at_position(mouse_position: Vector2) -> int: - if items.size() == 0: - return -1 - var labels_offset := v_scroll_bar.value var position := mouse_position.y + labels_offset var item_idx := labels_end_positions.bsearch(position) - # @DAM Fix this to return -1 if no item was selected. - return int(min(item_idx, items.size()-1)) # Return last item when position is below it. + if item_idx == items.size(): + item_idx = -1 + return item_idx func select(index: int): selected_idx = index - emit_signal("item_selected", selected_idx, items[selected_idx]) + selected_item = items[selected_idx] if selected_idx >= 0 && selected_idx < items.size() else "" + emit_signal("selection_changed", selected_idx, selected_item) -# @DAM Do we really need this? We should have just one signal to the selection. func unselect(): - selected_idx = -1 - emit_signal("nothing_selected") + select(-1) func pointer_input_on_press_handler(pointer: PointerInputSensor.PointerInputData): @@ -262,12 +266,7 @@ func pointer_input_on_end_drag_handler(pointer: PointerInputSensor.PointerInputD func pointer_input_on_click_handler(pointer: PointerInputSensor.PointerInputData): var item_idx := get_item_at_position(pointer.current_position - rect_global_position) - if item_idx >= 0: - select(item_idx) - emit_signal("item_selected", item_idx) - else: - unselect() - emit_signal("nothing_selected") + select(item_idx) func pointer_input_on_scroll_handler(pointer: PointerInputSensor.PointerInputData): @@ -278,7 +277,7 @@ func _clear_signals(): if clear_signals_on_hide == false: return - for signal_name in ["item_selected", "nothing_selected"]: + for signal_name in ["selection_changed"]: for it in get_signal_connection_list(signal_name): disconnect(it.signal, it.target, it.method) diff --git a/readme.md b/readme.md index 5d48eff..7b73115 100644 --- a/readme.md +++ b/readme.md @@ -57,9 +57,7 @@ Surgery Log - [x] Hide dialogs title bar (appear in the top with 1 or 2 pixels height); - [x] Fix automation to automatically disconnect acceptance signal handlers when reject is chosen on file_picker/confirm_action; - [x] Use popup to display confirmation messages; -- [ ] Improve option sets: - - [ ] selecting outside optionset entry should select none - - [ ] if no options are available, show nothing instead of "--" +- [x] Improve option sets: selecting outside optionset entry should select none; if no options are available, show nothing instead of "--"; - [ ] Fix back button: - on stage screen should show pop-up asking it changes are to be discarded; - on file-pickers screen should close them; -- cgit v1.2.3 From b12fe534ae3f2aea74f8c8506b3ad2fea321227c Mon Sep 17 00:00:00 2001 From: dam Date: Sat, 2 Apr 2022 08:49:21 +0000 Subject: Improved modal dialog messages. --- logic/database.gd | 2 +- logic/stage.gd | 2 +- menu/menu.gd | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) (limited to 'logic/stage.gd') diff --git a/logic/database.gd b/logic/database.gd index b952356..f3a5928 100644 --- a/logic/database.gd +++ b/logic/database.gd @@ -75,7 +75,7 @@ func delete_action(): if selected_idx < 0: return - dialog.setup("Do you want to delete entry with process ID '%s' from the database?" % db[selected_idx].process_id, "Yes, delete", "No") + dialog.setup("The entry with process ID '%s' will be deleted from the database." % db[selected_idx].process_id, "Yes, delete", "No") dialog.connect("accepted", self, "delete_action_confirmed") popup.open_popup("Delete entry?", dialog) diff --git a/logic/stage.gd b/logic/stage.gd index ef9ce3d..33b0644 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -108,7 +108,7 @@ func save_action(): func discard_action(): if get_stage().hash() != staged_entry_hash: - dialog.setup("Do you want to discard the changes made?", "Yes, discard", "No") + dialog.setup("Changes made to this entry will be discarded.", "Yes, discard", "No") dialog.connect("accepted", self, "discard_action_confirmed") popup.open_popup("Discard changes?", dialog) else: diff --git a/menu/menu.gd b/menu/menu.gd index 021c005..6bb5de0 100644 --- a/menu/menu.gd +++ b/menu/menu.gd @@ -69,7 +69,7 @@ func export_option_sets_action(): func clear_option_sets_action(): - dialog.setup("Do you want to delete all option sets?", "Yes, delete.", "No") + dialog.setup("All option sets from the dropdown menus will be deleted.", "Yes, delete.", "No") dialog.connect("accepted", stage, "clear_option_sets") popup.open_popup("Clear option sets?", dialog) @@ -85,7 +85,7 @@ func export_data_action(): func clear_data_action(): - dialog.setup("Do you want to delete all entries from the database?", "Yes, delete.", "No") + dialog.setup("All entries from the database will be deleted.", "Yes, delete.", "No") dialog.connect("accepted", database, "clear_database") popup.open_popup("Clear database?", dialog) @@ -97,7 +97,7 @@ func about_action(): # @DAM Hide this debug method before release. func test_fake_db_action(): - dialog.setup("Do you want to delete all entries from the database and replace by fake entries?", "Yes, replace.", "No") + dialog.setup("All entries from the database will be deleted and new fake entries inserted.", "Yes", "No") dialog.connect("accepted", database, "fake_database") popup.open_popup("Fake DB?", dialog) -- cgit v1.2.3 From 80733e322257656c7a93ad329e06fcda487da884 Mon Sep 17 00:00:00 2001 From: dam Date: Sat, 2 Apr 2022 09:41:54 +0000 Subject: Sets the stage action buttons side by side. --- logic/stage.gd | 8 ++++++-- main.tscn | 28 ++++++++++------------------ readme.md | 1 + 3 files changed, 17 insertions(+), 20 deletions(-) (limited to 'logic/stage.gd') diff --git a/logic/stage.gd b/logic/stage.gd index 33b0644..2101ad9 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -37,18 +37,22 @@ onready var pathology := get_node("controls/pathology") as OptionSet onready var intervention := get_node("controls/intervention") as OptionSet onready var is_urgency := get_node("controls/is_urgency") as Button onready var notes := get_node("controls/notes") as LineEdit -onready var save_button := get_node("controls/save") as Button -onready var discard_button := get_node("controls/discard") as Button +onready var save_button := get_node("controls/buttons/save") as Button +onready var discard_button := get_node("controls/buttons/discard") as Button onready var scrollbar := get_v_scrollbar() onready var popup := get_node("/root/main/popup") as ModalPopup onready var dialog := get_node("/root/main/dialog") as Dialog + func _init(): exclude_controls = ["date_picker", "save", "discard"] load_option_sets() func _ready(): + # Fix height of buttons container. + (get_node("controls/buttons") as Control).rect_min_size.y = save_button.rect_size.y + scrollbar.connect("visibility_changed", self, "adjust_layout") save_button.connect("pressed", self, "save_action") discard_button.connect("pressed", self, "discard_action") diff --git a/main.tscn b/main.tscn index d388cb5..ba50f42 100644 --- a/main.tscn +++ b/main.tscn @@ -206,29 +206,21 @@ placeholder_text = "Notas" caret_blink = true caret_blink_speed = 0.5 -[node name="discard" type="Button" parent="stage/controls" index="13"] +[node name="buttons" type="Control" parent="stage/controls" index="13"] margin_top = 994.0 margin_right = 1080.0 -margin_bottom = 1052.0 -grow_horizontal = 2 -grow_vertical = 2 -rect_min_size = Vector2( 0, 50 ) +margin_bottom = 994.0 + +[node name="discard" type="Button" parent="stage/controls/buttons"] +anchor_right = 0.5 text = "discard" -__meta__ = { -"_edit_use_anchors_": false -} -[node name="save" type="Button" parent="stage/controls" index="14"] -margin_top = 1056.0 -margin_right = 1080.0 -margin_bottom = 1114.0 -grow_horizontal = 2 -grow_vertical = 2 -rect_min_size = Vector2( 0, 50 ) +[node name="save" type="Button" parent="stage/controls/buttons"] +anchor_left = 0.5 +anchor_right = 1.0 +margin_bottom = 58.0 +grow_horizontal = 0 text = "save" -__meta__ = { -"_edit_use_anchors_": false -} [node name="file_picker" type="FileDialog" parent="."] anchor_right = 1.0 diff --git a/readme.md b/readme.md index 7b73115..aa1d545 100644 --- a/readme.md +++ b/readme.md @@ -58,6 +58,7 @@ Surgery Log - [x] Fix automation to automatically disconnect acceptance signal handlers when reject is chosen on file_picker/confirm_action; - [x] Use popup to display confirmation messages; - [x] Improve option sets: selecting outside optionset entry should select none; if no options are available, show nothing instead of "--"; +- [x] Set stage discard/save buttons side by side; - [ ] Fix back button: - on stage screen should show pop-up asking it changes are to be discarded; - on file-pickers screen should close them; -- cgit v1.2.3 From f40df1987a35adbc234a1b04b018d92d420b1e92 Mon Sep 17 00:00:00 2001 From: dam Date: Mon, 4 Apr 2022 10:02:13 +0000 Subject: Replaced icons and simplified dialog button labels. --- dialog/dialog.gd | 2 -- fonts/entypo-fontello.ttf | Bin 0 -> 6832 bytes fonts/font_icons.tres | 4 +-- logic/database.gd | 2 +- logic/stage.gd | 2 +- main.tscn | 66 ++++++++++++++++++++++++++++----------------- menu/menu.gd | 4 +-- option_set/option_set.tscn | 6 +++-- theme_dark.tres | 6 ----- themes/dark.tres | 6 +++++ 10 files changed, 58 insertions(+), 40 deletions(-) create mode 100644 fonts/entypo-fontello.ttf delete mode 100644 theme_dark.tres create mode 100644 themes/dark.tres (limited to 'logic/stage.gd') diff --git a/dialog/dialog.gd b/dialog/dialog.gd index bfdbab5..1cf0e66 100644 --- a/dialog/dialog.gd +++ b/dialog/dialog.gd @@ -19,7 +19,6 @@ func _init(): self.connect("hide", self, "_clear_signals") reject = Button.new() - reject.text = "reject" reject.anchor_top = 1.0 reject.anchor_left = 0.0 reject.anchor_right = 0.5 @@ -29,7 +28,6 @@ func _init(): add_child(reject) accept = Button.new() - accept.text = "accept" accept.anchor_top = 1.0 accept.anchor_left = 0.5 accept.anchor_right = 1.0 diff --git a/fonts/entypo-fontello.ttf b/fonts/entypo-fontello.ttf new file mode 100644 index 0000000..0e95ba4 Binary files /dev/null and b/fonts/entypo-fontello.ttf differ diff --git a/fonts/font_icons.tres b/fonts/font_icons.tres index 90f92cf..cc86d17 100644 --- a/fonts/font_icons.tres +++ b/fonts/font_icons.tres @@ -1,8 +1,8 @@ [gd_resource type="DynamicFont" load_steps=2 format=2] -[ext_resource path="res://fonts/B612-Regular.ttf" type="DynamicFontData" id=1] +[ext_resource path="res://fonts/entypo-fontello.ttf" type="DynamicFontData" id=1] [resource] -size = 128 +size = 64 use_filter = true font_data = ExtResource( 1 ) diff --git a/logic/database.gd b/logic/database.gd index f3a5928..ed39d43 100644 --- a/logic/database.gd +++ b/logic/database.gd @@ -75,7 +75,7 @@ func delete_action(): if selected_idx < 0: return - dialog.setup("The entry with process ID '%s' will be deleted from the database." % db[selected_idx].process_id, "Yes, delete", "No") + dialog.setup("The entry with process ID '%s' will be deleted from the database." % db[selected_idx].process_id, "Delete", "No") dialog.connect("accepted", self, "delete_action_confirmed") popup.open_popup("Delete entry?", dialog) diff --git a/logic/stage.gd b/logic/stage.gd index 2101ad9..104e888 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -112,7 +112,7 @@ func save_action(): func discard_action(): if get_stage().hash() != staged_entry_hash: - dialog.setup("Changes made to this entry will be discarded.", "Yes, discard", "No") + dialog.setup("Changes made to this entry will be discarded.", "Discard", "No") dialog.connect("accepted", self, "discard_action_confirmed") popup.open_popup("Discard changes?", dialog) else: diff --git a/main.tscn b/main.tscn index 7ee0c04..644d393 100644 --- a/main.tscn +++ b/main.tscn @@ -1,8 +1,8 @@ -[gd_scene load_steps=16 format=2] +[gd_scene load_steps=17 format=2] [ext_resource path="res://main.gd" type="Script" id=1] [ext_resource path="res://date_picker/date_picker.tscn" type="PackedScene" id=2] -[ext_resource path="res://theme_dark.tres" type="Theme" id=3] +[ext_resource path="res://themes/dark.tres" type="Theme" id=3] [ext_resource path="res://menu/menu.gd" type="Script" id=4] [ext_resource path="res://logic/database.gd" type="Script" id=5] [ext_resource path="res://fonts/font_icons.tres" type="DynamicFont" id=6] @@ -16,6 +16,20 @@ [ext_resource path="res://dialog/dialog.gd" type="Script" id=14] [ext_resource path="res://file_picker.gd" type="Script" id=15] +[sub_resource type="StyleBoxFlat" id=1] +content_margin_left = 15.0 +content_margin_right = 15.0 +content_margin_top = 15.0 +content_margin_bottom = 15.0 +bg_color = Color( 0, 0, 0, 0.75 ) +border_color = Color( 1, 1, 1, 1 ) +corner_radius_top_left = 50 +corner_radius_top_right = 50 +corner_radius_bottom_right = 50 +corner_radius_bottom_left = 50 +expand_margin_left = 7.0 +expand_margin_right = 7.0 + [node name="main" type="Control"] anchor_right = 1.0 anchor_bottom = 1.0 @@ -29,10 +43,13 @@ script = ExtResource( 5 ) [node name="menu" type="MenuButton" parent="database"] anchor_left = 1.0 anchor_right = 1.0 +margin_top = 32.0 margin_right = -32.0 grow_horizontal = 0 custom_fonts/font = ExtResource( 6 ) -text = "≡" +custom_styles/normal = SubResource( 1 ) +text = "" +flat = false script = ExtResource( 4 ) [node name="actions" type="VBoxContainer" parent="database"] @@ -46,39 +63,39 @@ grow_horizontal = 0 grow_vertical = 0 [node name="delete" type="Button" parent="database/actions"] -margin_right = 102.0 -margin_bottom = 162.0 +margin_right = 80.0 +margin_bottom = 95.0 focus_mode = 0 custom_fonts/font = ExtResource( 6 ) +custom_styles/normal = SubResource( 1 ) enabled_focus_mode = 0 -text = "" -flat = true +text = "" __meta__ = { "_edit_use_anchors_": false } [node name="edit" type="Button" parent="database/actions"] -margin_top = 166.0 -margin_right = 102.0 -margin_bottom = 328.0 +margin_top = 99.0 +margin_right = 80.0 +margin_bottom = 194.0 focus_mode = 0 custom_fonts/font = ExtResource( 6 ) +custom_styles/normal = SubResource( 1 ) enabled_focus_mode = 0 -text = "" -flat = true +text = "" __meta__ = { "_edit_use_anchors_": false } [node name="add" type="Button" parent="database/actions"] -margin_top = 332.0 -margin_right = 102.0 -margin_bottom = 494.0 +margin_top = 198.0 +margin_right = 80.0 +margin_bottom = 293.0 focus_mode = 0 custom_fonts/font = ExtResource( 6 ) +custom_styles/normal = SubResource( 1 ) enabled_focus_mode = 0 -text = "" -flat = true +text = "" __meta__ = { "_edit_use_anchors_": false } @@ -208,14 +225,14 @@ margin_bottom = 994.0 [node name="discard" type="Button" parent="stage/controls/buttons"] anchor_right = 0.5 -text = "discard" +text = "Discard" [node name="save" type="Button" parent="stage/controls/buttons"] anchor_left = 0.5 anchor_right = 1.0 margin_bottom = 58.0 grow_horizontal = 0 -text = "save" +text = "Save" [node name="file_picker" type="FileDialog" parent="."] anchor_right = 1.0 @@ -248,11 +265,11 @@ anchor_right = 0.95 anchor_bottom = 0.975 [node name="title" type="Label" parent="popup"] -anchor_left = 0.05 +anchor_left = 0.124 anchor_top = 0.025 anchor_right = 0.95 anchor_bottom = 0.1 -margin_left = 80.0 +margin_left = 0.0799866 margin_right = -80.0 margin_bottom = -20.0 align = 1 @@ -262,11 +279,12 @@ autowrap = true [node name="dismiss" type="Button" parent="popup"] anchor_left = 0.05 anchor_top = 0.025 -anchor_right = 0.95 +anchor_right = 0.124 anchor_bottom = 0.1 -margin_right = -892.0 +margin_right = 0.0799866 margin_bottom = -20.0 -text = "<" +custom_fonts/font = ExtResource( 6 ) +text = "" flat = true [node name="option_set_list" type="Control" parent="."] diff --git a/menu/menu.gd b/menu/menu.gd index 6bb5de0..5f13949 100644 --- a/menu/menu.gd +++ b/menu/menu.gd @@ -69,7 +69,7 @@ func export_option_sets_action(): func clear_option_sets_action(): - dialog.setup("All option sets from the dropdown menus will be deleted.", "Yes, delete.", "No") + dialog.setup("All option sets from the dropdown menus will be deleted.", "Delete all", "No") dialog.connect("accepted", stage, "clear_option_sets") popup.open_popup("Clear option sets?", dialog) @@ -85,7 +85,7 @@ func export_data_action(): func clear_data_action(): - dialog.setup("All entries from the database will be deleted.", "Yes, delete.", "No") + dialog.setup("All entries from the database will be deleted.", "Delete all", "No") dialog.connect("accepted", database, "clear_database") popup.open_popup("Clear database?", dialog) diff --git a/option_set/option_set.tscn b/option_set/option_set.tscn index 2b0bc76..5499361 100644 --- a/option_set/option_set.tscn +++ b/option_set/option_set.tscn @@ -1,6 +1,7 @@ -[gd_scene load_steps=2 format=2] +[gd_scene load_steps=3 format=2] [ext_resource path="res://option_set/option_set.gd" type="Script" id=1] +[ext_resource path="res://fonts/font_icons.tres" type="DynamicFont" id=2] [node name="option_set" type="Control"] anchor_right = 1.0 @@ -26,7 +27,8 @@ anchor_right = 1.0 anchor_bottom = 1.0 margin_left = -100.0 grow_horizontal = 0 -text = "▽" +custom_fonts/font = ExtResource( 2 ) +text = "" __meta__ = { "_edit_use_anchors_": false } diff --git a/theme_dark.tres b/theme_dark.tres deleted file mode 100644 index 832b226..0000000 --- a/theme_dark.tres +++ /dev/null @@ -1,6 +0,0 @@ -[gd_resource type="Theme" load_steps=2 format=2] - -[ext_resource path="res://fonts/font_regular.tres" type="DynamicFont" id=1] - -[resource] -default_font = ExtResource( 1 ) diff --git a/themes/dark.tres b/themes/dark.tres new file mode 100644 index 0000000..832b226 --- /dev/null +++ b/themes/dark.tres @@ -0,0 +1,6 @@ +[gd_resource type="Theme" load_steps=2 format=2] + +[ext_resource path="res://fonts/font_regular.tres" type="DynamicFont" id=1] + +[resource] +default_font = ExtResource( 1 ) -- cgit v1.2.3 From 2e70e18f24a14f74c00850bfc88071d23ec66da9 Mon Sep 17 00:00:00 2001 From: dam Date: Tue, 5 Apr 2022 05:33:23 +0000 Subject: WIP creating theme and improving UI/UX. --- dialog/dialog.gd | 2 + fonts/entypo-fontello.ttf | Bin 6832 -> 6560 bytes fonts/font_icons.tres | 2 +- logic/database.gd | 14 ++--- logic/stage.gd | 5 +- main.tscn | 136 ++++++++++++++++++++++++--------------------- option_set/option_set.tscn | 2 +- project.godot | 1 + themes/dark.tres | 61 +++++++++++++++++++- 9 files changed, 147 insertions(+), 76 deletions(-) (limited to 'logic/stage.gd') diff --git a/dialog/dialog.gd b/dialog/dialog.gd index 1cf0e66..71981c7 100644 --- a/dialog/dialog.gd +++ b/dialog/dialog.gd @@ -22,6 +22,7 @@ func _init(): reject.anchor_top = 1.0 reject.anchor_left = 0.0 reject.anchor_right = 0.5 + reject.margin_right = -5.0 reject.grow_vertical = Control.GROW_DIRECTION_BEGIN reject.name = "reject" reject.connect("pressed", self, "_signal_rejected") @@ -31,6 +32,7 @@ func _init(): accept.anchor_top = 1.0 accept.anchor_left = 0.5 accept.anchor_right = 1.0 + accept.margin_left = 5.0 accept.grow_vertical = Control.GROW_DIRECTION_BEGIN accept.name = "accept" accept.connect("pressed", self, "_signal_accepted") diff --git a/fonts/entypo-fontello.ttf b/fonts/entypo-fontello.ttf index 0e95ba4..345bd55 100644 Binary files a/fonts/entypo-fontello.ttf and b/fonts/entypo-fontello.ttf differ diff --git a/fonts/font_icons.tres b/fonts/font_icons.tres index cc86d17..6bc9f3d 100644 --- a/fonts/font_icons.tres +++ b/fonts/font_icons.tres @@ -3,6 +3,6 @@ [ext_resource path="res://fonts/entypo-fontello.ttf" type="DynamicFontData" id=1] [resource] -size = 64 +size = 55 use_filter = true font_data = ExtResource( 1 ) diff --git a/logic/database.gd b/logic/database.gd index ea27ef2..262a18a 100644 --- a/logic/database.gd +++ b/logic/database.gd @@ -44,18 +44,18 @@ func get_entry_view(database_entry: Dictionary) -> String: func item_selected(index: int): selected_idx = index - fade_action_buttons(1.0) + set_buttons_active(true) func clear_selection(): selected_idx = -1 unselect_all() - fade_action_buttons(0.25) + set_buttons_active(false) -func fade_action_buttons(target_alpha: float): - get_node("actions/delete").modulate.a = target_alpha - get_node("actions/edit").modulate.a = target_alpha +func set_buttons_active(active: bool): + (get_node("actions/delete") as Button).disabled = !active + (get_node("actions/edit") as Button).disabled = !active func delete_action(): @@ -83,14 +83,14 @@ func edit_action(): self.visible = false stage.visible = true var staged := (db[staged_idx] as Dictionary).duplicate(true) - stage.set_stage(staged) + stage.set_stage(staged, "Entry #%d" % (staged_idx+1)) func add_action(): self.visible = false stage.visible = true var staged := DatabaseEntry.instance_entry() - stage.set_stage(staged) + stage.set_stage(staged, "New entry") func save_stage(entry: Dictionary): diff --git a/logic/stage.gd b/logic/stage.gd index 104e888..e2248fa 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -23,7 +23,7 @@ const OPTION_SETS_TREE_STRUCTURE := { var staged_entry_hash: int var option_sets: Dictionary - +onready var title := get_node("controls/title") as Label onready var process_id := get_node("controls/process_id") as LineEdit onready var surgery_id := get_node("controls/surgery_id") as LineEdit onready var date := get_node("controls/date_picker") as DatePicker @@ -124,7 +124,8 @@ func discard_action_confirmed(): emit_signal("discard") -func set_stage(entry: Dictionary): +func set_stage(entry: Dictionary, title: String): + self.title.text = title staged_entry_hash = entry.hash() process_id.text = entry.process_id surgery_id.text = entry.surgery_id diff --git a/main.tscn b/main.tscn index 644d393..157f171 100644 --- a/main.tscn +++ b/main.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=17 format=2] +[gd_scene load_steps=16 format=2] [ext_resource path="res://main.gd" type="Script" id=1] [ext_resource path="res://date_picker/date_picker.tscn" type="PackedScene" id=2] @@ -12,7 +12,6 @@ [ext_resource path="res://logic/popup.gd" type="Script" id=10] [ext_resource path="res://touch_item_list/touch_item_list.tscn" type="PackedScene" id=11] [ext_resource path="res://touch_vertical_container/touch_vertical_container.tscn" type="PackedScene" id=12] -[ext_resource path="res://fonts/font_mono_regular.tres" type="DynamicFont" id=13] [ext_resource path="res://dialog/dialog.gd" type="Script" id=14] [ext_resource path="res://file_picker.gd" type="Script" id=15] @@ -37,7 +36,6 @@ theme = ExtResource( 3 ) script = ExtResource( 1 ) [node name="database" parent="." instance=ExtResource( 11 )] -custom_fonts/font = ExtResource( 13 ) script = ExtResource( 5 ) [node name="menu" type="MenuButton" parent="database"] @@ -63,11 +61,12 @@ grow_horizontal = 0 grow_vertical = 0 [node name="delete" type="Button" parent="database/actions"] -margin_right = 80.0 -margin_bottom = 95.0 +margin_right = 73.0 +margin_bottom = 86.0 focus_mode = 0 custom_fonts/font = ExtResource( 6 ) custom_styles/normal = SubResource( 1 ) +shortcut_in_tooltip = false enabled_focus_mode = 0 text = "" __meta__ = { @@ -75,12 +74,13 @@ __meta__ = { } [node name="edit" type="Button" parent="database/actions"] -margin_top = 99.0 -margin_right = 80.0 -margin_bottom = 194.0 +margin_top = 90.0 +margin_right = 73.0 +margin_bottom = 176.0 focus_mode = 0 custom_fonts/font = ExtResource( 6 ) custom_styles/normal = SubResource( 1 ) +shortcut_in_tooltip = false enabled_focus_mode = 0 text = "" __meta__ = { @@ -88,12 +88,13 @@ __meta__ = { } [node name="add" type="Button" parent="database/actions"] -margin_top = 198.0 -margin_right = 80.0 -margin_bottom = 293.0 +margin_top = 180.0 +margin_right = 73.0 +margin_bottom = 266.0 focus_mode = 0 custom_fonts/font = ExtResource( 6 ) custom_styles/normal = SubResource( 1 ) +shortcut_in_tooltip = false enabled_focus_mode = 0 text = "" __meta__ = { @@ -104,133 +105,140 @@ __meta__ = { visible = false script = ExtResource( 9 ) -[node name="process_id" type="LineEdit" parent="stage/controls" index="0"] +[node name="title" type="Label" parent="stage/controls" index="0"] margin_right = 1080.0 -margin_bottom = 62.0 +margin_bottom = 52.0 +align = 1 + +[node name="process_id" type="LineEdit" parent="stage/controls" index="1"] +margin_top = 56.0 +margin_right = 1080.0 +margin_bottom = 118.0 max_length = 32 -placeholder_text = "Nº Processo" +placeholder_text = "Process #" caret_blink = true caret_blink_speed = 0.5 -[node name="surgery_id" type="LineEdit" parent="stage/controls" index="1"] -margin_top = 66.0 +[node name="surgery_id" type="LineEdit" parent="stage/controls" index="2"] +margin_top = 122.0 margin_right = 1080.0 -margin_bottom = 128.0 +margin_bottom = 184.0 max_length = 32 -placeholder_text = "Nº Cirurgia" +placeholder_text = "Surgery #" caret_blink = true caret_blink_speed = 0.5 -[node name="date_picker" parent="stage/controls" index="2" instance=ExtResource( 2 )] +[node name="date_picker" parent="stage/controls" index="3" instance=ExtResource( 2 )] anchor_right = 0.0 anchor_bottom = 0.0 margin_left = 340.0 -margin_top = 132.0 +margin_top = 188.0 margin_right = 740.0 -margin_bottom = 332.0 +margin_bottom = 388.0 rect_min_size = Vector2( 400, 200 ) size_flags_horizontal = 4 -[node name="place" parent="stage/controls" index="3" instance=ExtResource( 7 )] +[node name="place" parent="stage/controls" index="4" instance=ExtResource( 7 )] anchor_right = 0.0 anchor_bottom = 0.0 -margin_top = 336.0 +margin_top = 392.0 margin_right = 1080.0 -margin_bottom = 398.0 +margin_bottom = 454.0 rect_min_size = Vector2( 0, 62 ) -placeholder = "Local" +placeholder = "Place" -[node name="anesthesia" parent="stage/controls" index="4" instance=ExtResource( 7 )] +[node name="anesthesia" parent="stage/controls" index="5" instance=ExtResource( 7 )] anchor_right = 0.0 anchor_bottom = 0.0 -margin_top = 402.0 +margin_top = 458.0 margin_right = 1080.0 -margin_bottom = 464.0 +margin_bottom = 520.0 rect_min_size = Vector2( 0, 62 ) -placeholder = "Anestesia" +placeholder = "Anesthesia" -[node name="first_assistant" parent="stage/controls" index="5" instance=ExtResource( 7 )] +[node name="first_assistant" parent="stage/controls" index="6" instance=ExtResource( 7 )] anchor_right = 0.0 anchor_bottom = 0.0 -margin_top = 468.0 +margin_top = 524.0 margin_right = 1080.0 -margin_bottom = 530.0 +margin_bottom = 586.0 rect_min_size = Vector2( 0, 62 ) -placeholder = "1º Ajudante" +placeholder = "First assistant" -[node name="type" parent="stage/controls" index="6" instance=ExtResource( 7 )] +[node name="type" parent="stage/controls" index="7" instance=ExtResource( 7 )] anchor_right = 0.0 anchor_bottom = 0.0 -margin_top = 534.0 +margin_top = 590.0 margin_right = 1080.0 -margin_bottom = 596.0 +margin_bottom = 652.0 rect_min_size = Vector2( 0, 62 ) -placeholder = "Tipo" +placeholder = "Type" -[node name="sub_type" parent="stage/controls" index="7" instance=ExtResource( 7 )] +[node name="sub_type" parent="stage/controls" index="8" instance=ExtResource( 7 )] anchor_right = 0.0 anchor_bottom = 0.0 -margin_top = 600.0 +margin_top = 656.0 margin_right = 1080.0 -margin_bottom = 662.0 +margin_bottom = 718.0 rect_min_size = Vector2( 0, 62 ) -placeholder = "Sub-tipo" +placeholder = "Sub-type" -[node name="sub_sub_type" parent="stage/controls" index="8" instance=ExtResource( 7 )] +[node name="sub_sub_type" parent="stage/controls" index="9" instance=ExtResource( 7 )] anchor_right = 0.0 anchor_bottom = 0.0 -margin_top = 666.0 +margin_top = 722.0 margin_right = 1080.0 -margin_bottom = 728.0 +margin_bottom = 784.0 rect_min_size = Vector2( 0, 62 ) -placeholder = "Sub-sub-tipo" +placeholder = "Sub-sub-type" -[node name="pathology" parent="stage/controls" index="9" instance=ExtResource( 7 )] +[node name="pathology" parent="stage/controls" index="10" instance=ExtResource( 7 )] anchor_right = 0.0 anchor_bottom = 0.0 -margin_top = 732.0 +margin_top = 788.0 margin_right = 1080.0 -margin_bottom = 794.0 +margin_bottom = 850.0 rect_min_size = Vector2( 0, 62 ) -placeholder = "Patologia" +placeholder = "Pathology" -[node name="intervention" parent="stage/controls" index="10" instance=ExtResource( 7 )] +[node name="intervention" parent="stage/controls" index="11" instance=ExtResource( 7 )] anchor_right = 0.0 anchor_bottom = 0.0 -margin_top = 798.0 +margin_top = 854.0 margin_right = 1080.0 -margin_bottom = 860.0 +margin_bottom = 916.0 rect_min_size = Vector2( 0, 62 ) -placeholder = "Intervenção" +placeholder = "Intervention" -[node name="is_urgency" type="CheckBox" parent="stage/controls" index="11"] -margin_top = 864.0 +[node name="is_urgency" type="CheckBox" parent="stage/controls" index="12"] +margin_top = 920.0 margin_right = 1080.0 -margin_bottom = 924.0 +margin_bottom = 972.0 text = "Urgência" -[node name="notes" type="LineEdit" parent="stage/controls" index="12"] -margin_top = 928.0 +[node name="notes" type="LineEdit" parent="stage/controls" index="13"] +margin_top = 976.0 margin_right = 1080.0 -margin_bottom = 990.0 +margin_bottom = 1038.0 max_length = 4096 -placeholder_text = "Notas" +placeholder_text = "Notes" caret_blink = true caret_blink_speed = 0.5 -[node name="buttons" type="Control" parent="stage/controls" index="13"] -margin_top = 994.0 +[node name="buttons" type="Control" parent="stage/controls" index="14"] +margin_top = 1042.0 margin_right = 1080.0 -margin_bottom = 994.0 +margin_bottom = 1042.0 [node name="discard" type="Button" parent="stage/controls/buttons"] anchor_right = 0.5 +margin_right = -5.0 text = "Discard" [node name="save" type="Button" parent="stage/controls/buttons"] anchor_left = 0.5 anchor_right = 1.0 -margin_bottom = 58.0 +margin_left = 5.0 grow_horizontal = 0 text = "Save" diff --git a/option_set/option_set.tscn b/option_set/option_set.tscn index 5499361..3d09674 100644 --- a/option_set/option_set.tscn +++ b/option_set/option_set.tscn @@ -28,7 +28,7 @@ anchor_bottom = 1.0 margin_left = -100.0 grow_horizontal = 0 custom_fonts/font = ExtResource( 2 ) -text = "" +text = "" __meta__ = { "_edit_use_anchors_": false } diff --git a/project.godot b/project.godot index 4a3e1ed..36429fc 100644 --- a/project.godot +++ b/project.godot @@ -115,4 +115,5 @@ common/enable_pause_aware_picking=true [rendering] vram_compression/import_etc=true +environment/default_clear_color=Color( 0, 0, 0, 1 ) environment/default_environment="res://default_env.tres" diff --git a/themes/dark.tres b/themes/dark.tres index 832b226..717b9c2 100644 --- a/themes/dark.tres +++ b/themes/dark.tres @@ -1,6 +1,65 @@ -[gd_resource type="Theme" load_steps=2 format=2] +[gd_resource type="Theme" load_steps=9 format=2] [ext_resource path="res://fonts/font_regular.tres" type="DynamicFont" id=1] +[ext_resource path="res://fonts/font_mono_regular.tres" type="DynamicFont" id=2] + +[sub_resource type="StyleBoxFlat" id=2] +bg_color = Color( 0.11, 0.11, 0.11, 1 ) +corner_radius_top_left = 8 +corner_radius_top_right = 8 +corner_radius_bottom_right = 8 +corner_radius_bottom_left = 8 + +[sub_resource type="StyleBoxFlat" id=8] +bg_color = Color( 0.109804, 0.109804, 0.109804, 1 ) +border_width_left = 3 +border_width_top = 3 +border_width_right = 3 +border_width_bottom = 3 +border_color = Color( 0.223529, 0.352941, 0.501961, 1 ) +corner_radius_top_left = 8 +corner_radius_top_right = 8 +corner_radius_bottom_right = 8 +corner_radius_bottom_left = 8 + +[sub_resource type="StyleBoxFlat" id=4] +bg_color = Color( 0.223529, 0.352941, 0.501961, 1 ) +corner_radius_top_left = 8 +corner_radius_top_right = 8 +corner_radius_bottom_right = 8 +corner_radius_bottom_left = 8 + +[sub_resource type="StyleBoxEmpty" id=5] + +[sub_resource type="StyleBoxFlat" id=9] +content_margin_left = 10.0 +content_margin_right = 10.0 +content_margin_top = 5.0 +content_margin_bottom = 5.0 +bg_color = Color( 0, 0, 0, 0 ) +draw_center = false +border_width_bottom = 3 +border_color = Color( 0.223529, 0.352941, 0.501961, 1 ) + +[sub_resource type="StyleBoxFlat" id=7] +content_margin_left = 10.0 +content_margin_right = 10.0 +content_margin_top = 5.0 +content_margin_bottom = 5.0 +bg_color = Color( 0, 0, 0, 0 ) +draw_center = false +border_width_bottom = 3 +border_color = Color( 0.33, 0.33, 0.33, 1 ) [resource] default_font = ExtResource( 1 ) +Button/styles/disabled = null +Button/styles/focus = SubResource( 2 ) +Button/styles/hover = SubResource( 8 ) +Button/styles/normal = SubResource( 2 ) +Button/styles/pressed = SubResource( 4 ) +ItemList/fonts/font = ExtResource( 2 ) +ItemList/styles/bg = SubResource( 5 ) +LineEdit/styles/focus = SubResource( 9 ) +LineEdit/styles/normal = SubResource( 7 ) +LineEdit/styles/read_only = null -- cgit v1.2.3 From 1fe674aec4bd6ce3fc666c8545dadb4f0614067d Mon Sep 17 00:00:00 2001 From: dam Date: Wed, 6 Apr 2022 22:52:14 +0000 Subject: Improved file operations for database and option sets. Added confirmation dialogs when importing database and option sets files. Standardized the error print calls. Fixed capitalization on menu entries. --- dialog/dialog.gd | 58 +++++++-------- logic/database.gd | 145 ++++++++++++++++++++++++------------- logic/database_entry.gd | 6 +- logic/popup.gd | 12 +-- logic/stage.gd | 85 ++++++++++++++++------ menu/menu.gd | 63 ++++++++-------- touch_item_list/touch_item_list.gd | 2 +- 7 files changed, 231 insertions(+), 140 deletions(-) (limited to 'logic/stage.gd') diff --git a/dialog/dialog.gd b/dialog/dialog.gd index a22a3a6..89fee5c 100644 --- a/dialog/dialog.gd +++ b/dialog/dialog.gd @@ -8,37 +8,37 @@ signal rejected # () export var clear_signals_on_hide := true var message : Label -var accept : Button -var reject : Button +var accept_button : Button +var reject_button : Button func _init(): - self.anchor_right = 1.0 - self.anchor_bottom = 1.0 - self.rect_clip_content = true + self.anchor_right = 1.0 + self.anchor_bottom = 1.0 + self.rect_clip_content = true self.connect("hide", self, "_clear_signals") - reject = Button.new() - reject.anchor_top = 1.0 - reject.anchor_left = 0.0 - reject.anchor_right = 0.5 - reject.margin_right = -5.0 - reject.rect_min_size.y = 62.0 - reject.grow_vertical = Control.GROW_DIRECTION_BEGIN - reject.name = "reject" - reject.connect("pressed", self, "_signal_rejected") - add_child(reject) + reject_button = Button.new() + reject_button.anchor_top = 1.0 + reject_button.anchor_left = 0.0 + reject_button.anchor_right = 0.5 + reject_button.margin_right = -5.0 + reject_button.rect_min_size.y = 62.0 + reject_button.grow_vertical = Control.GROW_DIRECTION_BEGIN + reject_button.name = "reject" + reject_button.connect("pressed", self, "_signal_rejected") + add_child(reject_button) - accept = Button.new() - accept.anchor_top = 1.0 - accept.anchor_left = 0.5 - accept.anchor_right = 1.0 - accept.margin_left = 5.0 - accept.rect_min_size.y = 62.0 - accept.grow_vertical = Control.GROW_DIRECTION_BEGIN - accept.name = "accept" - accept.connect("pressed", self, "_signal_accepted") - add_child(accept) + accept_button = Button.new() + accept_button.anchor_top = 1.0 + accept_button.anchor_left = 0.5 + accept_button.anchor_right = 1.0 + accept_button.margin_left = 5.0 + accept_button.rect_min_size.y = 62.0 + accept_button.grow_vertical = Control.GROW_DIRECTION_BEGIN + accept_button.name = "accept" + accept_button.connect("pressed", self, "_signal_accepted") + add_child(accept_button) message = Label.new() message.autowrap = true @@ -71,9 +71,9 @@ func _signal_accepted(): func setup(message: String, accept_label: String = "Accept", reject_label: String = "Reject"): self.message.text = message - accept.visible = accept_label != "" - accept.text = accept_label - reject.visible = reject_label != "" - reject.text = reject_label + accept_button.visible = accept_label != "" + accept_button.text = accept_label + reject_button.visible = reject_label != "" + reject_button.text = reject_label diff --git a/logic/database.gd b/logic/database.gd index 262a18a..8acf1b6 100644 --- a/logic/database.gd +++ b/logic/database.gd @@ -8,7 +8,7 @@ var db: Array var selected_idx: int var staged_idx: int -onready var stage := get_node("/root/main/stage") as Stage +onready var stage := get_node("/root/main/stage") # as Stage @DAM Commented to avoid cyclic dependency. onready var delete_button := get_node("actions/delete") as Button onready var edit_button := get_node("actions/edit") as Button onready var add_button := get_node("actions/add") as Button @@ -18,7 +18,6 @@ onready var dialog := get_node("/root/main/dialog") as Dialog func _init(): selected_idx = -1 staged_idx = -1 - load_database() func _ready(): @@ -32,10 +31,7 @@ func _ready(): stage.connect("save", self, "save_stage") stage.connect("discard", self, "discard_stage") - for it in db: - self.add_item(get_entry_view(it)) - - clear_selection() + load_database() func get_entry_view(database_entry: Dictionary) -> String: @@ -62,7 +58,7 @@ func delete_action(): if selected_idx < 0: return - dialog.setup("The entry with process ID '%s' will be deleted from the database." % db[selected_idx].process_id, "Delete", "No") + dialog.setup("The entry #%d with process ID '%s' will be deleted from the database." % [selected_idx+1, db[selected_idx].process_id], "Delete", "No") dialog.connect("accepted", self, "delete_action_confirmed") popup.open_popup("Delete entry?", dialog) @@ -95,8 +91,8 @@ func add_action(): func save_stage(entry: Dictionary): if DatabaseEntry.is_valid_entry(entry) == false: - printerr("INVALID ENTRY RECEIVED") - return # @DAM Deal with this. + printerr("Invalid entry detected.") + return var next_selected_idx: int if staged_idx >= 0: @@ -125,7 +121,89 @@ func discard_stage(): grab_focus() +func save_database(file_path: String = DATABASE_FILE_PATH): + match file_path.get_extension(): + "json": + export_json(file_path, db) + + "csv": + export_csv(file_path, db) + + _: + printerr("Invalid database file extension: '%s'." % file_path.get_file()) + return + + +static func export_json(file_path: String, data: Array): + var database_file := { + "version": DATABASE_FILE_VERSION, + "database": data, + } + var indentation_char := "" if file_path == DATABASE_FILE_PATH else "\t" + var file_content := JSON.print(database_file, indentation_char) + + var file := File.new() + file.open(file_path, File.WRITE) + file.store_string(file_content) + file.close() + + +static func export_csv(file_path: String, data: Array): + var file := File.new() + file.open(file_path, File.WRITE) + var header := PoolStringArray(DatabaseEntry.ENTRY_PROTOTYPE.keys()) + file.store_csv_line(header) + for it in data: + file.store_csv_line(it.values()) + file.close() + + func load_database(file_path: String = DATABASE_FILE_PATH): + match file_path.get_extension(): + "json": + clear_database() + db = import_json(file_path) + if file_path != DATABASE_FILE_PATH: + sanitize_database(db) + + "csv": + clear_database() + db = import_csv(file_path) + + _: + printerr("Invalid database file extension: '%s'." % file_path.get_file()) + return + + for it in db: + # The JSON specification does not define integer or float types, but only a number type. + # Therefore, converting a Variant to JSON text will convert all numerical values to float types. + # Thus, we cast all integer values once we load them. + it["date_year"] = int(it["date_year"]) + it["date_month"] = int(it["date_month"]) + it["date_day"] = int(it["date_day"]) + + self.add_item(get_entry_view(it)) + + +static func sanitize_database(database: Array): + var blueprint := DatabaseEntry.ENTRY_PROTOTYPE + + for entry in database: + # Delete extra keys. + var keys_to_delete: Array + for key in entry: + if blueprint.has(key) == false: + keys_to_delete.append(key) + for key in keys_to_delete: + entry.erase(key) + + # Fix wrong value types. + for key in blueprint: + if entry.has(key) == false || typeof(entry[key]) != typeof(blueprint[key]): + entry[key] = blueprint[key] + + +static func import_json(file_path: String) -> Array: var file := File.new() file.open(file_path, File.READ_WRITE) var file_content = file.get_as_text() @@ -133,43 +211,21 @@ func load_database(file_path: String = DATABASE_FILE_PATH): var parse_result = JSON.parse(file_content) if parse_result.error != OK || typeof(parse_result.result) != TYPE_DICTIONARY: - push_error("Failed to parse database file: '%s'.") - return + printerr("Failed to parse database file: '%s'.") + return [] if parse_result.result["version"] != DATABASE_FILE_VERSION: - push_error("Invalid database file version '%s', expected '%s'." % DATABASE_FILE_VERSION) - return + printerr("Invalid database file version '%s', expected '%s'." % DATABASE_FILE_VERSION) + return [] if typeof(parse_result.result["database"]) != TYPE_ARRAY: - push_error("Failed to load database file contents.") - return - - db = parse_result.result["database"] - - # The JSON specification does not define integer or float types, but only a number type. - # Therefore, converting a Variant to JSON text will convert all numerical values to float types. - # Thus, we cast all integer values once we load them. - for it in db: - it.date_year = int(it.date_year) - it.date_month = int(it.date_month) - it.date_day = int(it.date_day) - - -func save_database(file_path: String = DATABASE_FILE_PATH): - var database_file := { - "version": DATABASE_FILE_VERSION, - "database": db, - } - var indentation_char := "" if file_path == DATABASE_FILE_PATH else "\t" - var file_content := JSON.print(database_file, indentation_char) + printerr("Failed to load database file contents.") + return [] - var file := File.new() - file.open(file_path, File.WRITE) - file.store_string(file_content) - file.close() + return parse_result.result["database"] -static func import_database(file_path: String) -> Array: +static func import_csv(file_path: String) -> Array: var result: Array var file := File.new() file.open(file_path, File.READ_WRITE) @@ -196,17 +252,6 @@ static func import_database(file_path: String) -> Array: return result -static func export_database(file_path: String, database: Array = db): - var file := File.new() - file.open(file_path, File.WRITE) - var header := PoolStringArray(DatabaseEntry.ENTRY_PROTOTYPE.keys()) - file.store_csv_line(header) - for it in database: - # @DAM This approach depends on the order the dictionary fields are created. - file.store_csv_line(it.values()) - file.close() - - func clear_database(): clear_selection() self.clear() diff --git a/logic/database_entry.gd b/logic/database_entry.gd index 496b752..8b0c51f 100644 --- a/logic/database_entry.gd +++ b/logic/database_entry.gd @@ -6,9 +6,9 @@ const DATE_FORMAT: String = "%04d-%02d-%02d" const ENTRY_PROTOTYPE: Dictionary = { "process_id": "", "surgery_id": "", - "date_year": 0, - "date_month": 0, - "date_day": 0, + "date_year": 1, + "date_month": 1, + "date_day": 1, "place": "", "anesthesia": "", "first_assistant": "", diff --git a/logic/popup.gd b/logic/popup.gd index d7c98f9..95fc2c3 100644 --- a/logic/popup.gd +++ b/logic/popup.gd @@ -5,12 +5,12 @@ signal dismissed # () export var clear_signals_on_hide := true -var control : Control -var control_parent : Node +var control : Control +var control_parent : Node -onready var title := get_node("title") as Label -onready var background := get_node("background") as Panel -onready var dismiss := get_node("dismiss") as Button +onready var title := get_node("title") as Label +onready var background := get_node("background") as Panel +onready var dismiss_button := get_node("dismiss") as Button func _init(): @@ -21,7 +21,7 @@ func _init(): func _ready(): - dismiss.connect("pressed", self, "dismiss") + dismiss_button.connect("pressed", self, "dismiss") func _clear_signals(): diff --git a/logic/stage.gd b/logic/stage.gd index e2248fa..764ee52 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -105,7 +105,7 @@ func show_options(field: String): func save_action(): self.visible = false var staged_entry := get_stage() - gather_option_sets(staged_entry) + gather_option_sets(staged_entry, option_sets) save_option_sets() emit_signal("save", staged_entry) @@ -164,7 +164,7 @@ func get_stage() -> Dictionary: return entry -func sanitize_option_sets(entry: Dictionary, blueprint: Dictionary = OPTION_SETS_TREE_STRUCTURE): +static func sanitize_option_sets(entry: Dictionary, blueprint: Dictionary = OPTION_SETS_TREE_STRUCTURE): # Delete extra keys. var keys_to_delete: Array for key in entry: @@ -185,7 +185,7 @@ func sanitize_option_sets(entry: Dictionary, blueprint: Dictionary = OPTION_SETS sanitize_option_sets(entry[key][sub_key], blueprint[key]) -func gather_option_sets(entry: Dictionary, target: Dictionary = option_sets, blueprint: Dictionary = OPTION_SETS_TREE_STRUCTURE): +static func gather_option_sets(entry: Dictionary, target: Dictionary, blueprint: Dictionary = OPTION_SETS_TREE_STRUCTURE): for key in blueprint: if target.get(key) == null: target[key] = {} @@ -201,7 +201,55 @@ func gather_option_sets(entry: Dictionary, target: Dictionary = option_sets, blu gather_option_sets(entry, target[key][value], blueprint[key]) +func save_option_sets(file_path: String = OPTION_SETS_FILE_PATH): + match file_path.get_extension(): + "json": + export_json(file_path, option_sets) + +# "csv": +# export_csv(file_path, option_sets) + + _: + printerr("Invalid option sets file extension: '%s'." % file_path.get_file()) + return + + +static func export_json(file_path: String, data: Dictionary): + var option_sets_file := { + "version": OPTION_SETS_FILE_VERSION, + "option_sets": data, + } + var indentation_char := "" if file_path == OPTION_SETS_FILE_PATH else "\t" + var file_content := JSON.print(option_sets_file, indentation_char) + + var file := File.new() + file.open(file_path, File.WRITE) + file.store_string(file_content) + file.close() + + +#static func export_csv(file_path: String, data: Dictionary): +# pass + + func load_option_sets(file_path: String = OPTION_SETS_FILE_PATH): + match file_path.get_extension(): + "json": + clear_option_sets() + option_sets = import_json(file_path) + if file_path != OPTION_SETS_FILE_PATH: + sanitize_option_sets(option_sets) + + "csv": + clear_option_sets() + option_sets = import_csv(file_path) + + _: + printerr("Invalid option sets file extension: '%s'." % file_path.get_file()) + return + + +static func import_json(file_path: String) -> Dictionary: var file := File.new() file.open(file_path, File.READ_WRITE) var file_content = file.get_as_text() @@ -209,32 +257,25 @@ func load_option_sets(file_path: String = OPTION_SETS_FILE_PATH): var parse_result = JSON.parse(file_content) if parse_result.error != OK || typeof(parse_result.result) != TYPE_DICTIONARY: - push_error("Failed to parse option sets file: '%s'.") - return + printerr("Failed to parse option sets file: '%s'.") + return {} if parse_result.result["version"] != OPTION_SETS_FILE_VERSION: - push_error("Invalid option sets file version '%s', expected '%s'." % OPTION_SETS_FILE_VERSION) - return + printerr("Invalid option sets file version '%s', expected '%s'." % OPTION_SETS_FILE_VERSION) + return {} if typeof(parse_result.result["option_sets"]) != TYPE_DICTIONARY: - push_error("Failed to load option sets file contents.") - return + printerr("Failed to load option sets file contents.") + return {} - option_sets = parse_result.result["option_sets"] + return parse_result.result["option_sets"] -func save_option_sets(file_path: String = OPTION_SETS_FILE_PATH): - var option_sets_file := { - "version": OPTION_SETS_FILE_VERSION, - "option_sets": option_sets, - } - var indentation_char := "" if file_path == OPTION_SETS_FILE_PATH else "\t" - var file_content := JSON.print(option_sets_file, indentation_char) - - var file := File.new() - file.open(file_path, File.WRITE) - file.store_string(file_content) - file.close() +static func import_csv(file_path: String) -> Dictionary: + var result := {} + for it in Database.import_csv(file_path): + gather_option_sets(it, result) + return result func clear_option_sets(): diff --git a/menu/menu.gd b/menu/menu.gd index 5f13949..735f86a 100644 --- a/menu/menu.gd +++ b/menu/menu.gd @@ -1,12 +1,13 @@ extends MenuButton const menu_items: Array = [ - { label = "IMPORT OPTION SETS", action = "import_option_sets_action" }, - { label = "EXPORT OPTION SETS", action = "export_option_sets_action" }, - { label = "CLEAR OPTION SETS", action = "clear_option_sets_action" }, - { label = "EXPORT DATA", action = "export_data_action" }, - { label = "CLEAR DATA", action = "clear_data_action" }, - { label = "ABOUT", action = "about_action" }, + { label = "Import Option Sets", action = "import_option_sets_action" }, + { label = "Export Option Sets", action = "export_option_sets_action" }, + { label = "Clear Option Sets", action = "clear_option_sets_action" }, + { label = "Import Database", action = "import_database_action" }, + { label = "Export Database", action = "export_database_action" }, + { label = "Clear Database", action = "clear_database_action" }, + { label = "About", action = "about_action" }, { label = "TEST_FAKE_DB", action = "test_fake_db_action" }, ] const license_font_b612: String = "res://licenses/font_b612.txt" @@ -31,39 +32,27 @@ func id_pressed(id: int): func import_option_sets_action(): + dialog.setup("All option sets from the dropdown menus will be replaced.", "Continue", "No") + dialog.connect("accepted", self, "import_option_sets_action_accepted") + popup.open_popup("Replace option sets?", dialog) + + +func import_option_sets_action_accepted(): file_picker.mode = FileDialog.MODE_OPEN_FILE file_picker.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS) file_picker.filters = ["*.json", "*.csv"] file_picker.current_file = "" - file_picker.connect("file_selected", self, "import_option_sets_action_confirmed") + file_picker.connect("file_selected", stage, "load_option_sets") file_picker.show_modal(true) file_picker.invalidate() -func import_option_sets_action_confirmed(file_path: String): - match file_path.get_extension(): - "json": - stage.load_option_sets(file_path) - stage.sanitize_option_sets(stage.option_sets) - stage.save_option_sets() - - "csv": - var database := Database.import_database(file_path) - for it in database: - stage.gather_option_sets(it) - stage.save_option_sets() - - _: - push_error("Invalid file extension selected to be parsed for option sets: '%s'." % file_path.get_file()) - return - - func export_option_sets_action(): file_picker.mode = FileDialog.MODE_SAVE_FILE file_picker.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS) file_picker.filters = ["*.json"] file_picker.current_file = "" - file_picker.connect("file_selected", stage, "save_option_sets", [], CONNECT_ONESHOT) + file_picker.connect("file_selected", stage, "save_option_sets") file_picker.show_modal(true) file_picker.invalidate() @@ -74,17 +63,33 @@ func clear_option_sets_action(): popup.open_popup("Clear option sets?", dialog) -func export_data_action(): +func import_database_action(): + dialog.setup("All entries from the database will be replaced.", "Continue", "No") + dialog.connect("accepted", self, "import_database_action_accepted") + popup.open_popup("Replace database?", dialog) + + +func import_database_action_accepted(): + file_picker.mode = FileDialog.MODE_OPEN_FILE + file_picker.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS) + file_picker.filters = ["*.json", "*.csv"] + file_picker.current_file = "" + file_picker.connect("file_selected", database, "load_database") + file_picker.show_modal(true) + file_picker.invalidate() + + +func export_database_action(): file_picker.mode = FileDialog.MODE_SAVE_FILE file_picker.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS) file_picker.filters = ["*.csv"] file_picker.current_file = "" - file_picker.connect("file_selected", database, "save_database", [], CONNECT_ONESHOT) + file_picker.connect("file_selected", database, "save_database") file_picker.show_modal(true) file_picker.invalidate() -func clear_data_action(): +func clear_database_action(): dialog.setup("All entries from the database will be deleted.", "Delete all", "No") dialog.connect("accepted", database, "clear_database") popup.open_popup("Clear database?", dialog) diff --git a/touch_item_list/touch_item_list.gd b/touch_item_list/touch_item_list.gd index 67f88d7..6794138 100644 --- a/touch_item_list/touch_item_list.gd +++ b/touch_item_list/touch_item_list.gd @@ -3,7 +3,7 @@ class_name TouchItemList const POINTER_VELOCITY_DECAYING_FACTOR: float = PI const POINTER_VELOCITY_BOOST_FACTOR: float = 1.25 -const EXACT_SELECTION: bool = false +const EXACT_SELECTION: bool = true var is_pointer_dragging := false var pointer_drag_velocity := 0.0 -- cgit v1.2.3 From e2b74f51400ab4bbf7bc788ed2129378e35911a4 Mon Sep 17 00:00:00 2001 From: dam Date: Sat, 9 Apr 2022 23:21:38 +0000 Subject: Automatically save database and option sets when loaded from non-base locations. --- logic/database.gd | 3 +++ logic/stage.gd | 3 +++ 2 files changed, 6 insertions(+) (limited to 'logic/stage.gd') diff --git a/logic/database.gd b/logic/database.gd index a22c964..19216f6 100644 --- a/logic/database.gd +++ b/logic/database.gd @@ -183,6 +183,9 @@ func load_database(file_path: String = DATABASE_FILE_PATH): it["date_day"] = int(it["date_day"]) self.add_item(get_entry_view(it)) + + if file_path != DATABASE_FILE_PATH: + save_database() static func sanitize_database(database: Array): diff --git a/logic/stage.gd b/logic/stage.gd index 764ee52..a09f611 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -247,6 +247,9 @@ func load_option_sets(file_path: String = OPTION_SETS_FILE_PATH): _: printerr("Invalid option sets file extension: '%s'." % file_path.get_file()) return + + if file_path != OPTION_SETS_FILE_PATH: + save_option_sets() static func import_json(file_path: String) -> Dictionary: -- cgit v1.2.3 From e32cef1fee6eabd6db71af71356664d259c5eb2b Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 10 Apr 2022 06:53:01 +0000 Subject: Removed auto-save logic from load_database and load_option_sets; Changed menu logic to save database and optionsets after making changes. --- logic/database.gd | 35 ++++++++++++++++------------------- logic/stage.gd | 3 --- menu/menu.gd | 36 ++++++++++++++++++++++++------------ 3 files changed, 40 insertions(+), 34 deletions(-) (limited to 'logic/stage.gd') diff --git a/logic/database.gd b/logic/database.gd index 19216f6..92b707b 100644 --- a/logic/database.gd +++ b/logic/database.gd @@ -183,9 +183,6 @@ func load_database(file_path: String = DATABASE_FILE_PATH): it["date_day"] = int(it["date_day"]) self.add_item(get_entry_view(it)) - - if file_path != DATABASE_FILE_PATH: - save_database() static func sanitize_database(database: Array): @@ -261,21 +258,21 @@ func clear_database(): db.resize(0) -func fake_database(): - clear_database() - for idx in range(500): - var today := OS.get_date(true) - var date_year = today.year + int(float(idx) / 30.0 / 12) - var date_month = 1 + int(float(idx) / 30.0) % 12 - var date_day = 1 + (idx % 30) - var fake_entry = DatabaseEntry.instance_entry({ - "process_id": "%06d" % idx, - "surgery_id": "s%05d" % idx, - "date_year": date_year, - "date_month": date_month, - "date_day": date_day, - }) - db.append(fake_entry) - self.add_item(get_entry_view(fake_entry)) +#func DEBUG_create_fake_database(): +# clear_database() +# for idx in range(500): +# var today := OS.get_date(true) +# var date_year = today.year + int(float(idx) / 30.0 / 12) +# var date_month = 1 + int(float(idx) / 30.0) % 12 +# var date_day = 1 + (idx % 30) +# var fake_entry = DatabaseEntry.instance_entry({ +# "process_id": "%06d" % idx, +# "surgery_id": "s%05d" % idx, +# "date_year": date_year, +# "date_month": date_month, +# "date_day": date_day, +# }) +# db.append(fake_entry) +# self.add_item(get_entry_view(fake_entry)) diff --git a/logic/stage.gd b/logic/stage.gd index a09f611..764ee52 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -247,9 +247,6 @@ func load_option_sets(file_path: String = OPTION_SETS_FILE_PATH): _: printerr("Invalid option sets file extension: '%s'." % file_path.get_file()) return - - if file_path != OPTION_SETS_FILE_PATH: - save_option_sets() static func import_json(file_path: String) -> Dictionary: diff --git a/menu/menu.gd b/menu/menu.gd index c3e3185..d6e1501 100644 --- a/menu/menu.gd +++ b/menu/menu.gd @@ -8,7 +8,6 @@ const menu_items: Array = [ { label = "Export Database", action = "export_database_action" }, { label = "Clear Database", action = "clear_database_action" }, { label = "About", action = "about_action" }, -# { label = "TEST_FAKE_DB", action = "test_fake_db_action" }, ] const license_font_b612: String = "res://licenses/font_b612.txt" const license_godot: String = "res://licenses/godot.txt" @@ -42,11 +41,16 @@ func import_option_sets_action_accepted(): file_picker.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS) file_picker.filters = ["*.json", "*.csv"] file_picker.current_file = "" - file_picker.connect("file_selected", stage, "load_option_sets") + file_picker.connect("file_selected", self, "import_option_sets") file_picker.show_modal(true) file_picker.invalidate() +func import_option_sets(file_path: String): + stage.load_option_sets(file_path) + stage.save_option_sets() + + func export_option_sets_action(): file_picker.mode = FileDialog.MODE_SAVE_FILE file_picker.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS) @@ -59,10 +63,15 @@ func export_option_sets_action(): func clear_option_sets_action(): dialog.setup("All option sets from the dropdown menus will be deleted.", "Delete all", "No") - dialog.connect("accepted", stage, "clear_option_sets") + dialog.connect("accepted", self, "clear_option_sets") popup.open_popup("Clear option sets?", dialog) +func clear_option_sets(): + stage.clear_option_sets() + stage.save_option_sets() + + func import_database_action(): dialog.setup("All entries from the database will be replaced.", "Continue", "No") dialog.connect("accepted", self, "import_database_action_accepted") @@ -74,11 +83,16 @@ func import_database_action_accepted(): file_picker.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS) file_picker.filters = ["*.json", "*.csv"] file_picker.current_file = "" - file_picker.connect("file_selected", database, "load_database") + file_picker.connect("file_selected", self, "import_database") file_picker.show_modal(true) file_picker.invalidate() +func import_database(file_path: String): + database.load_database(file_path) + database.save_database() + + func export_database_action(): file_picker.mode = FileDialog.MODE_SAVE_FILE file_picker.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS) @@ -91,19 +105,17 @@ func export_database_action(): func clear_database_action(): dialog.setup("All entries from the database will be deleted.", "Delete all", "No") - dialog.connect("accepted", database, "clear_database") + dialog.connect("accepted", self, "clear_database") popup.open_popup("Clear database?", dialog) +func clear_database(): + database.clear_database() + database.save_database() + + func about_action(): dialog.setup("Surgery Log\nversion 0.1", "", "") popup.open_popup("About", dialog) -# @DAM Hide this debug method before release. -func test_fake_db_action(): - dialog.setup("All entries from the database will be deleted and new fake entries inserted.", "Yes", "No") - dialog.connect("accepted", database, "fake_database") - popup.open_popup("Fake DB?", dialog) - - -- cgit v1.2.3 From 75791aecbff0d8adc1011f45a69877cabff616e0 Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 10 Apr 2022 08:05:58 +0000 Subject: Renamed dropdown button on option_set. --- logic/database.gd | 2 +- logic/stage.gd | 2 +- option_set/option_set.gd | 2 +- option_set/option_set.tscn | 2 +- readme.md | 2 -- 5 files changed, 4 insertions(+), 6 deletions(-) (limited to 'logic/stage.gd') diff --git a/logic/database.gd b/logic/database.gd index 2f091b6..04f617f 100644 --- a/logic/database.gd +++ b/logic/database.gd @@ -8,7 +8,7 @@ var db: Array var selected_idx: int var staged_idx: int -onready var stage := get_node("/root/main/stage") # as Stage @DAM Commented to avoid cyclic dependency. +onready var stage := get_node("/root/main/stage") # as Stage # Commented to avoid cyclic dependencies. onready var delete_button := get_node("actions/delete") as Button onready var edit_button := get_node("actions/edit") as Button onready var add_button := get_node("actions/add") as Button diff --git a/logic/stage.gd b/logic/stage.gd index 764ee52..747cccf 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -74,7 +74,7 @@ func _ready(): "intervention": intervention } for key in option_sets_map: - var button := option_sets_map[key].get_node("options") as Button # @DAM Maybe rename "options". Also requires rename on option_set. + var button := option_sets_map[key].get_node("button") as Button button.connect("pressed", self, "show_options", [key]) diff --git a/option_set/option_set.gd b/option_set/option_set.gd index bb37736..25ca0ff 100644 --- a/option_set/option_set.gd +++ b/option_set/option_set.gd @@ -14,7 +14,7 @@ func get_text() -> String: var selected_idx: int onready var input := get_node("input") as LineEdit -onready var button := get_node("options") as Button # @DAM Maybe rename this. Also requires renaming on stage. +onready var button := get_node("button") as Button onready var popup := get_node("/root/main/popup") as ModalPopup onready var options := get_node("/root/main/option_set_list") as OptionSetList diff --git a/option_set/option_set.tscn b/option_set/option_set.tscn index 3d09674..26a042d 100644 --- a/option_set/option_set.tscn +++ b/option_set/option_set.tscn @@ -21,7 +21,7 @@ __meta__ = { "_edit_use_anchors_": false } -[node name="options" type="Button" parent="."] +[node name="button" type="Button" parent="."] anchor_left = 1.0 anchor_right = 1.0 anchor_bottom = 1.0 diff --git a/readme.md b/readme.md index c347666..223a5c7 100644 --- a/readme.md +++ b/readme.md @@ -66,8 +66,6 @@ Surgery Log - on auto-fill pop-up, should close it; - on database screen, should deselect selected item, otherwise should quit the app; - [x] db entry should be printed as "%9s | %4s | %yyyy-%MM"; -- [ ] Adjust theme: - - [ ] Increase scrollbar width (in content margin property) - [ ] Create light theme; - [ ] Create theme entry in menu and save on settings file; - [ ] Cleanup code: -- cgit v1.2.3 From 7348ca93ccc1f4a55438d59430f7f870c08b56d6 Mon Sep 17 00:00:00 2001 From: dam Date: Sat, 16 Apr 2022 15:14:29 +0000 Subject: Added details to error messages when loading JSON files for database and option sets. --- logic/database.gd | 34 ++++++++++++++++++++++------------ logic/stage.gd | 34 ++++++++++++++++++++++------------ 2 files changed, 44 insertions(+), 24 deletions(-) (limited to 'logic/stage.gd') diff --git a/logic/database.gd b/logic/database.gd index 04f617f..ed80ae7 100644 --- a/logic/database.gd +++ b/logic/database.gd @@ -2,7 +2,7 @@ extends TouchItemList class_name Database const DATABASE_FILE_PATH: String = "user://database.json" -const DATABASE_FILE_VERSION: int = 1 +const DATABASE_FILE_VERSION: String = "SL_DB_V1" var db: Array var selected_idx: int @@ -130,7 +130,7 @@ func save_database(file_path: String = DATABASE_FILE_PATH): export_csv(file_path, db) _: - printerr("Invalid database file extension: '%s'." % file_path.get_file()) + printerr("Invalid database file extension '%s', expected 'json' or 'csv'." % file_path.get_file()) return @@ -171,7 +171,7 @@ func load_database(file_path: String = DATABASE_FILE_PATH): db = import_csv(file_path) _: - printerr("Invalid database file extension: '%s'." % file_path.get_file()) + printerr("Invalid database file extension '%s', expected 'json' or 'csv'." % file_path.get_file()) return for it in db: @@ -204,25 +204,35 @@ static func sanitize_database(database: Array): static func import_json(file_path: String) -> Array: + var result := [] + var file := File.new() - file.open(file_path, File.READ_WRITE) + var error := file.open(file_path, File.READ_WRITE) + if error != OK: + printerr("Failed to open database file '%s' (error %d)." % [file_path, error]) + return result var file_content = file.get_as_text() file.close() var parse_result = JSON.parse(file_content) - if parse_result.error != OK || typeof(parse_result.result) != TYPE_DICTIONARY: - printerr("Failed to parse database file: '%s'.") - return [] + if parse_result.error != OK: + printerr("Failed to parse database file '%s' (error %d)." % [file_path, parse_result.error]) + return result + + if typeof(parse_result.result) != TYPE_DICTIONARY: + printerr("Invalid database file type '%s', expected '%s'." % [typeof(parse_result.result), TYPE_DICTIONARY]) + return result if parse_result.result["version"] != DATABASE_FILE_VERSION: - printerr("Invalid database file version '%s', expected '%s'." % DATABASE_FILE_VERSION) - return [] + printerr("Invalid database file version '%s', expected '%s'." % [parse_result.result["version"], DATABASE_FILE_VERSION]) + return result if typeof(parse_result.result["database"]) != TYPE_ARRAY: - printerr("Failed to load database file contents.") - return [] + printerr("Invalid database content type '%s', expected '%s'." % [typeof(parse_result.result["database"]), TYPE_ARRAY]) + return result - return parse_result.result["database"] + result = parse_result.result["database"] + return result static func import_csv(file_path: String) -> Array: diff --git a/logic/stage.gd b/logic/stage.gd index 747cccf..e6d9474 100644 --- a/logic/stage.gd +++ b/logic/stage.gd @@ -5,7 +5,7 @@ signal save # (database_entry: Dictionary) signal discard # () const OPTION_SETS_FILE_PATH: String = "user://option_sets.json" -const OPTION_SETS_FILE_VERSION: int = 1 +const OPTION_SETS_FILE_VERSION: String = "SL_OS_V1" const OPTION_SETS_NOT_AVAILABLE: String = "--" const OPTION_SETS_TREE_STRUCTURE := { "place": null, @@ -210,7 +210,7 @@ func save_option_sets(file_path: String = OPTION_SETS_FILE_PATH): # export_csv(file_path, option_sets) _: - printerr("Invalid option sets file extension: '%s'." % file_path.get_file()) + printerr("Invalid option sets file extension '%s', expected 'json'." % file_path.get_file()) return @@ -245,30 +245,40 @@ func load_option_sets(file_path: String = OPTION_SETS_FILE_PATH): option_sets = import_csv(file_path) _: - printerr("Invalid option sets file extension: '%s'." % file_path.get_file()) + printerr("Invalid option sets file extension '%s', expected 'json' or 'csv'." % file_path.get_file()) return static func import_json(file_path: String) -> Dictionary: + var result := {} + var file := File.new() - file.open(file_path, File.READ_WRITE) + var error := file.open(file_path, File.READ_WRITE) + if error != OK: + printerr("Failed to open option sets file '%s' (error %d)." % [file_path, error]) + return result var file_content = file.get_as_text() file.close() var parse_result = JSON.parse(file_content) - if parse_result.error != OK || typeof(parse_result.result) != TYPE_DICTIONARY: - printerr("Failed to parse option sets file: '%s'.") - return {} + if parse_result.error != OK: + printerr("Failed to parse option sets file '%s' (error %d)." % [file_path, parse_result.error]) + return result + + if typeof(parse_result.result) != TYPE_DICTIONARY: + printerr("Invalid option sets file type '%s', expected '%s'." % [typeof(parse_result.result), TYPE_DICTIONARY]) + return result if parse_result.result["version"] != OPTION_SETS_FILE_VERSION: - printerr("Invalid option sets file version '%s', expected '%s'." % OPTION_SETS_FILE_VERSION) - return {} + printerr("Invalid option sets file version '%s', expected '%s'." % [parse_result.result["version"], OPTION_SETS_FILE_VERSION]) + return result if typeof(parse_result.result["option_sets"]) != TYPE_DICTIONARY: - printerr("Failed to load option sets file contents.") - return {} + printerr("Invalid option sets content type '%s', expected '%s'." % [typeof(parse_result.result["option_sets"]), TYPE_DICTIONARY]) + return result - return parse_result.result["option_sets"] + result = parse_result.result["option_sets"] + return result static func import_csv(file_path: String) -> Dictionary: -- cgit v1.2.3