diff --git a/assets/Student_list.csv b/assets/Student_list.csv index e05d8f5..064b71e 100644 --- a/assets/Student_list.csv +++ b/assets/Student_list.csv @@ -1,6 +1,7 @@ First Name,Last Name,Sex,Tutorial 1,Tutorial 2,Extended Applications,Numpy & MatPlotLib,SciPy,Monte Carlo,Pandas & Seaborn,Folium Abdalaziz,Abunjaila,Male,30.5,15,18,28,17,17,17,22 Marleen,Adolphi,Female,29.5,15,18,32,19,0,17,24 +Skofiare,Berisha,Female,29.5,13,18,34,20,17,20,26 Aurela,Brahimi,Female,17.5,15,15.5,26,16,17,19,16 Cam Thu,Do,Female,31,15,18,34,19,20,21.5,22 Nova,Eib,Female,31,15,15,34,20,20,21,27 diff --git a/assets/Student_list.xlsx b/assets/Student_list.xlsx index 403425c..25d18a9 100644 Binary files a/assets/Student_list.xlsx and b/assets/Student_list.xlsx differ diff --git a/assets/test.db b/assets/test.db deleted file mode 100644 index b29087f..0000000 Binary files a/assets/test.db and /dev/null differ diff --git a/class_editor.py b/class_editor.py index de1d775..1751ea3 100644 --- a/class_editor.py +++ b/class_editor.py @@ -1,5 +1,4 @@ -import imgui - +from imgui_bundle import imgui, imgui_ctx from model import * class ClassEditor: @@ -11,7 +10,7 @@ class ClassEditor: def __call__(self): classes = Class.select() - with imgui.begin("Class Editor", False, imgui.WINDOW_NO_RESIZE | imgui.WINDOW_NO_COLLAPSE): + with imgui_ctx.begin("Class Editor"): imgui.text("Add Class") _, self.add_name = imgui.input_text(" ", self.add_name) diff --git a/database_editor.py b/database_editor.py new file mode 100644 index 0000000..78c76a4 --- /dev/null +++ b/database_editor.py @@ -0,0 +1,261 @@ +from imgui_bundle import imgui, imgui_ctx, ImVec2 +from model import * +import random + +class DatabaseEditor: + def __init__(self): + super().__init__() + self.add_name = str() + self.select_class = 0 + self.select_lecture = 0 + self.select_student = 0 + self.select_submission = 0 + + self.class_name = str() + self.add_class_name = str() + + self.student_prename = str() + self.student_surname = str() + self.student_sex = False + self.add_student_prename = str() + self.add_student_surname = str() + self.add_student_sex = False + + self.lecture_title = str() + self.lecture_points = 0 + self.add_lecture_title = str() + self.add_lecture_points = 0 + + self.submission_points = 0.0 + self.add_submission_lecture = 0 + self.add_submission_points = 0.0 + + def content_list(self, content: list, selector: int, id: str, height: int) -> int: + w = imgui.get_window_size().x + with imgui_ctx.begin_child(str(id), ImVec2(w*0.3, height)): + for n, c in enumerate(content, start = 1): + _, clicked = imgui.selectable(f"{n}. {c}", selector == n-1) + if clicked: + selector = n-1 + return selector + + def class_editor(self): + w, h = imgui.get_window_size() + classes = Class.select() + content = [f"{c.name}" for c in classes] + self.select_class = self.content_list(content, self.select_class, "class_content", h*0.15) + imgui.same_line() + + with imgui_ctx.begin_child("Class", ImVec2(w*0.25, h*0.15)): + imgui.text("Edit Class") + _, self.class_name = imgui.input_text_with_hint("##class_edit1", content[self.select_class], self.class_name) + + if imgui.button("Update"): + id = classes[self.select_class].id + Class.update(name=self.class_name).where(Class.id == id).execute() + + imgui.same_line() + + if imgui.button("Delete"): + id = classes[self.select_class].id + students = Student.select().where(Student.class_id == id) + for student in students: + Submission.delete().where(Submission.student_id == student.id).execute() + Student.delete().where(Student.class_id == id).execute() + Lecture.delete().where(Lecture.class_id == id).execute() + Class.delete().where(Class.id == id).execute() + + + imgui.separator() + + imgui.text("Add Class") + _, self.add_class_name = imgui.input_text_with_hint("##class_edit2", "Class Name", self.add_class_name) + + if imgui.button("Add"): + Class.create(name=self.add_class_name) + + return classes[self.select_class].id + + def student_editor(self, class_id: int): + w, h = imgui.get_window_size() + students = Student.select().where(Student.class_id == class_id) + content = [f"{s.prename} {s.surname}" for s in students] + self.select_student = self.content_list(content, self.select_student, "student_content", h*0.45) + imgui.same_line() + + with imgui_ctx.begin_child("Student", ImVec2(w*0.25, h*0.4)): + imgui.text("Edit Student") + + prename = students[self.select_student].prename + _, self.student_prename = imgui.input_text_with_hint("##student_edit1", prename, self.student_prename) + + surname = students[self.select_student].surname + _, self.student_surname = imgui.input_text_with_hint("##student_edit2", surname, self.student_surname) + + if imgui.radio_button("Male##1", not self.student_sex): + self.student_sex = not self.student_sex + imgui.same_line() + if imgui.radio_button("Female##1", self.student_sex): + self.student_sex = not self.student_sex + + if imgui.button("Update"): + Student.update( + prename = self.student_prename, + surname = self.student_surname, + sex = "Female" if self.student_sex else "Male" + ).where(Student.id == students[self.select_student].id).execute() + + imgui.same_line() + + if imgui.button("Delete"): + id = students[self.select_student].id + Student.delete().where(Student.id == id).execute() + Submission.delete().where(Submission.student_id == id).execute() + self.select_student = 0 + + imgui.separator() + + imgui.text("Add Student") + _, self.add_student_prename = imgui.input_text_with_hint("##student_edit3", "First Name", self.add_student_prename) + _, self.add_student_surname = imgui.input_text_with_hint("##student_edit4", "Last Name", self.add_student_surname) + + if imgui.radio_button("Male##2", not self.add_student_sex): + self.add_student_sex = not self.add_student_sex + imgui.same_line() + if imgui.radio_button("Female##2", self.add_student_sex): + self.add_student_sex = not self.add_student_sex + + if imgui.button("Add"): + Student.create( + prename=self.add_student_prename, + surname=self.add_student_surname, + sex="Female" if self.add_student_sex else "Male", + class_id=class_id + ) + self.add_student_prename = str() + self.add_student_surname = str() + self.add_student_sex = False + + return students[self.select_student].id + + def lecture_editor(self, class_id: int): + w, h = imgui.get_window_size() + lectures = Lecture.select().where(Lecture.class_id == class_id) + content = [f"{l.title}" for l in lectures] + self.select_lecture = self.content_list(content, self.select_lecture, "lecture_content", h*0.15) + imgui.same_line() + + with imgui_ctx.begin_child("Lecture", ImVec2(w*0.25, h*0.15)): + imgui.text("Edit Lecture") + _, self.lecture_title = imgui.input_text_with_hint("##lecture_edit1", content[self.select_lecture], self.lecture_title) + _, self.lecture_points = imgui.input_int("##lecture_points1", self.lecture_points) + if self.lecture_points < 0: + self.lecture_points = 0 + + if imgui.button("Update"): + Lecture.update(title=self.lecture_title, points=self.lecture_points).where(Lecture.id == lectures[self.select_lecture].id).execute() + + imgui.same_line() + + if imgui.button("Delete"): + id = lectures[self.select_lecture].id + Submission.delete().where(Submission.lecture_id == id).execute() + Lecture.delete().where(Lecture.id == id).execute() + + imgui.separator() + + imgui.text("Add Lecture") + _, self.add_lecture_title = imgui.input_text_with_hint("##lecture_edit2", "Lecture Title", self.add_lecture_title) + _, self.add_lecture_points = imgui.input_int("##lecture_points2", self.add_lecture_points) + if self.add_lecture_points < 0: + self.add_lecture_points = 0 + + if imgui.button("Add"): + Lecture.create(title=self.add_lecture_title, points=self.add_lecture_points, class_id=class_id) + + return lectures[self.select_lecture].id + + def submission_editor(self, student_id: int): + w, h = imgui.get_window_size() + submissions = Submission.select().where(Submission.student_id == student_id) + lectures = [Lecture.get_by_id(sub.lecture_id) for sub in submissions] + content = [l.title for l in lectures] + self.select_submission = self.content_list(content, self.select_submission, "submission_content", h*0.2) + imgui.same_line() + + with imgui_ctx.begin_child("Submission", ImVec2(w*0.25, h*0.2)): + imgui.text("Edit Submission") + imgui.text(content[self.select_submission]) + + points = submissions[self.select_submission].points + if points.is_integer(): + points = int(points) + + max_points = lectures[self.select_submission].points + + _, self.submission_points = imgui.input_float(f"{points}/{max_points}", self.submission_points, 0.5, 10, "%.1f") + if self.submission_points < 0: + self.submission_points = 0 + + if imgui.button("Update"): + Submission.update(points=self.submission_points).where(Submission.id == submissions[self.select_submission].id).execute() + + imgui.same_line() + + if imgui.button("Delete"): + Submission.delete().where(Submission.id == submissions[self.select_submission].id).execute() + + imgui.separator() + + imgui.text("Add Submission") + available_lectures = Lecture.select().where(Lecture.class_id == Student.get_by_id(student_id).class_id) + combo_items = [l.title for l in available_lectures] + _, self.add_submission_lecture = imgui.combo("##lecture_combo", self.add_submission_lecture, combo_items, len(combo_items)) + _, self.add_submission_points = imgui.input_float("##lecture_title", self.add_submission_points, 0.5, 10, "%.1f") + if self.add_submission_points < 0: + self.add_submission_points = 0 + + if imgui.button("Add"): + Submission.create( + points=self.add_submission_points, + lecture_id=available_lectures[self.add_submission_lecture].id, + student_id=student_id + ) + + return submissions[self.select_submission].id + + def __call__(self): + with imgui_ctx.begin("Database Editor"): + class_id = self.class_editor() + imgui.separator() + self.lecture_editor(class_id) + imgui.separator() + student_id = self.student_editor(class_id) + imgui.separator() + self.submission_editor(student_id) + return + classes = Class.select() + + with imgui_ctx.begin("Database Editor"): + imgui.text("Add Class") + + _, self.add_name = imgui.input_text(" ", self.add_name) + + if imgui.button("Add"): + if self.add_name: + Class.create(name=self.add_name) + self.add_name = str() + + imgui.separator() + + if not classes: + imgui.text("No Dataset could be queried") + return + + for n, c in enumerate(classes, start=1): + display = f"{n}. {c.name}" + opened, _ = imgui.selectable(display, self.select == n-1) + if opened: + self.select = n-1 + + return classes[self.select] diff --git a/demo_docking.py b/demo_docking.py new file mode 100644 index 0000000..1be190e --- /dev/null +++ b/demo_docking.py @@ -0,0 +1,926 @@ +# A more complex app demo +# +# It demonstrates how to: +# - set up a complex docking layouts (with several possible layouts): +# - load additional fonts, possibly colored, and with emojis +# - display a log window +# - use the status bar +# - use default menus (App and view menu), and how to customize them +# - use a specific application state (instead of using static variables) +# - save some additional user settings within imgui ini file +# - use borderless windows, that are movable and resizable +import json +from enum import Enum +import time + +from imgui_bundle import hello_imgui, icons_fontawesome_6, imgui, immapp, imgui_ctx, ImVec4, ImVec2 +from imgui_bundle.demos_python import demo_utils +from typing import List, Any + + +########################################################################## +# Our Application State +########################################################################## +class MyAppSettings: + motto: hello_imgui.InputTextData + value: int = 10 + + def __init__(self): + self.motto = hello_imgui.InputTextData( + "Hello, Dear ImGui\n" + "Unleash your creativity!\n", + True, # multiline + (14.0, 3.0) # initial size (in em) + ) + +class RocketState(Enum): + Init = 0 + Preparing = 1 + Launched = 2 + + +# Struct that holds the application's state +class AppState: + f: float + counter: int + rocket_progress: float + my_app_settings: MyAppSettings + rocket_state: RocketState + rocket_launch_time: float + + title_font: imgui.ImFont + color_font: imgui.ImFont + emoji_font: imgui.ImFont + large_icon_font: imgui.ImFont + + def __init__(self): + self.f = 0 + self.counter = 0 + self.rocket_progress = 0.0 + self.rocket_launch_time = 0.0 + self.my_app_settings = MyAppSettings() + self.rocket_state = RocketState.Init + + +########################################################################## +# Additional fonts handling +########################################################################## +def load_fonts(app_state: AppState): # This is called by runnerParams.callbacks.LoadAdditionalFonts + # First, load the default font (the default font should be loaded first) + # In this example, we instruct HelloImGui to use FontAwesome6 instead of FontAwesome4 + hello_imgui.get_runner_params().callbacks.default_icon_font = hello_imgui.DefaultIconFont.font_awesome6 + hello_imgui.imgui_default_settings.load_default_font_with_font_awesome_icons() + + # Load the title font + # app_state.title_font = hello_imgui.load_font("fonts/DroidSans.ttf", 18.0) + font_loading_params_title_icons = hello_imgui.FontLoadingParams() + font_loading_params_title_icons.merge_font_awesome = True + app_state.title_font = hello_imgui.load_font("fonts/Roboto/Roboto-BoldItalic.ttf", 18, font_loading_params_title_icons) + + # Load the emoji font + font_loading_params_emoji = hello_imgui.FontLoadingParams() + font_loading_params_emoji.use_full_glyph_range = True + app_state.emoji_font = hello_imgui.load_font("fonts/NotoEmoji-Regular.ttf", 24., font_loading_params_emoji) + + # Load a large icon font + font_loading_params_large_icon = hello_imgui.FontLoadingParams() + font_loading_params_large_icon.use_full_glyph_range = True + app_state.large_icon_font = hello_imgui.load_font("fonts/fontawesome-webfont.ttf", 24., font_loading_params_large_icon) + + # Load a colored font + font_loading_params_color = hello_imgui.FontLoadingParams() + font_loading_params_color.load_color = True + app_state.color_font = hello_imgui.load_font("fonts/Playbox/Playbox-FREE.otf", 24., font_loading_params_color) + + + +########################################################################## +# Save additional settings in the ini file +########################################################################## +# This demonstrates how to store additional info in the application settings +# Use this sparingly! +# This is provided as a convenience only, and it is not intended to store large quantities of text data. + +# Warning, the save/load function below are quite simplistic! +def my_app_settings_to_string(settings: MyAppSettings) -> str: + as_dict: dict[str, Any] = {} + as_dict["motto"] = hello_imgui.input_text_data_to_dict(settings.motto) + as_dict["value"] = settings.value + return json.dumps(as_dict) + + +def string_to_my_app_settings(s: str) -> MyAppSettings: + r = MyAppSettings() + try: + as_dict = json.loads(s) + r.motto = hello_imgui.input_text_data_from_dict(as_dict["motto"]) + r.value = as_dict["value"] + except Exception as e: + hello_imgui.log(hello_imgui.LogLevel.error, f"Error while loading user settings: {e}") + return r + + +def load_my_app_settings(app_state: AppState): + """ + Note: load_my_app_settings() and save_my_app_settings() will be called in the callbacks `post_init` & `before_exit` + runner_params.callbacks.post_init = lambda: load_user_settings(app_state) + runner_params.callbacks.before_exit = lambda: save_user_settings(app_state) + """ + app_state.my_app_settings = string_to_my_app_settings( + hello_imgui.load_user_pref("MyAppSettings") + ) + + +def save_my_app_settings(app_state: AppState): + hello_imgui.save_user_pref( + "MyAppSettings", my_app_settings_to_string(app_state.my_app_settings) + ) + + +########################################################################## +# Gui functions used in this demo +########################################################################## +@immapp.static(last_hide_time=1) +def demo_hide_window(app_state: AppState): + # Display a button that will hide the application window + imgui.push_font(app_state.title_font) + imgui.text("Hide app window") + imgui.pop_font() + + if imgui.button("Hide"): + demo_hide_window.last_hide_time = time.time() + hello_imgui.get_runner_params().app_window_params.hidden = True + if imgui.is_item_hovered(): + imgui.set_tooltip("By clicking this button, you can hide the window for 3 seconds.") + if demo_hide_window.last_hide_time > 0.0: + now = time.time() + if now - demo_hide_window.last_hide_time > 3.0: + demo_hide_window.last_hide_time = -1.0 + hello_imgui.get_runner_params().app_window_params.hidden = False + + +# Display a button that will add another dockable window during execution +def demo_show_additional_window(app_state: AppState): + # In order to add a dockable window during execution, you should use + # hello_imgui.add_dockable_window() + # Note: you should not modify manually the content of runnerParams.docking_params.dockable_windows + # (since HelloImGui is constantly looping on it) + + imgui.push_font(app_state.title_font) + imgui.text("Dynamically add window") + imgui.pop_font() + + window_name = "Additional Window" + if imgui.button("Show additional window"): + additional_window = hello_imgui.DockableWindow() + additional_window.label = window_name + additional_window.include_in_view_menu = False # this window is not shown in the view menu, + additional_window.remember_is_visible = False # its visibility is not saved in the settings file, + additional_window.dock_space_name = "MiscSpace" # when shown, it will appear in MiscSpace. + additional_window.gui_function = lambda: imgui.text("This is the additional window") + hello_imgui.add_dockable_window( + additional_window, + force_dockspace=False # means that the window will be docked to the last space it was docked to + # i.e. dock_space_name is ignored if the user previously moved the window to another space + ) + imgui.set_item_tooltip("By clicking this button, you can show an additional window") + + if imgui.button("Remove additional window"): + hello_imgui.remove_dockable_window(window_name) + imgui.set_item_tooltip("By clicking this button, you can remove the additional window") + + +def demo_basic_widgets(app_state: AppState): + imgui.push_font(app_state.title_font) + imgui.text("Basic widgets demo") + imgui.pop_font() + + imgui.begin_group() + # Edit a float using a slider from 0.0 to 1.0 + changed, app_state.f = imgui.slider_float("float", app_state.f, 0.0, 1.0) + if changed: + hello_imgui.log( + hello_imgui.LogLevel.warning, f"state.f was changed to {app_state.f}" + ) + + # Buttons return true when clicked (most widgets return true when edited/activated) + if imgui.button("Button"): + app_state.counter += 1 + hello_imgui.log(hello_imgui.LogLevel.info, "Button was pressed") + imgui.same_line() + imgui.text(f"counter = {app_state.counter}") + imgui.end_group() + + if imgui.is_item_hovered(): + imgui.set_tooltip("These widgets will interact with the log window") + + +def demo_user_settings(app_state: AppState): + imgui.push_font(app_state.title_font) + imgui.text("User settings") + imgui.pop_font() + + imgui.begin_group() + + imgui.set_next_item_width(hello_imgui.em_size(7.0)) + _, app_state.my_app_settings.value = imgui.slider_int( + "Value", app_state.my_app_settings.value, 0, 100 + ) + + _ = hello_imgui.input_text_resizable("Motto", app_state.my_app_settings.motto) + imgui.text("(this text widget is resizable)") + + imgui.end_group() + if imgui.is_item_hovered(): + imgui.set_tooltip("The values below are stored in the application settings ini file and restored at startup") + + +def demo_rocket(app_state: AppState): + imgui.push_font(app_state.title_font) + imgui.text("Rocket demo") + imgui.pop_font() + + imgui.begin_group() + if app_state.rocket_state == RocketState.Init: + if imgui.button(f"{icons_fontawesome_6.ICON_FA_ROCKET} Launch rocket"): + app_state.rocket_launch_time = time.time() + app_state.rocket_state = RocketState.Preparing + hello_imgui.log(hello_imgui.LogLevel.warning, "Rocket is being prepared") + elif app_state.rocket_state == RocketState.Preparing: + imgui.text("Please Wait") + app_state.rocket_progress = (time.time() - app_state.rocket_launch_time) / 3.0 + if app_state.rocket_progress >= 1.0: + app_state.rocket_state = RocketState.Launched + hello_imgui.log(hello_imgui.LogLevel.warning, "Rocket was launched") + elif app_state.rocket_state == RocketState.Launched: + imgui.text(f"{icons_fontawesome_6.ICON_FA_ROCKET} Rocket launched") + if imgui.button("Reset Rocket"): + app_state.rocket_state = RocketState.Init + app_state.rocket_progress = 0.0 + imgui.end_group() + if imgui.is_item_hovered(): + imgui.set_tooltip("Look at the status bar after clicking") + + +def demo_docking_flags(app_state: AppState): + imgui.push_font(app_state.title_font) + imgui.text("Main dock space node flags") + imgui.pop_font() + imgui.text_wrapped( + """ +This will edit the ImGuiDockNodeFlags for "MainDockSpace". +Most flags are inherited by children dock spaces. + """ + ) + + class DockFlagWithInfo: + def __init__(self, flag, label, tip): + self.flag = flag + self.label = label + self.tip = tip + + all_flags = [ + DockFlagWithInfo( + imgui.DockNodeFlags_.no_docking_split, + "NoSplit", + "prevent Dock Nodes from being split", + ), + DockFlagWithInfo( + imgui.DockNodeFlags_.no_resize, + "NoResize", + "prevent Dock Nodes from being resized", + ), + DockFlagWithInfo( + imgui.DockNodeFlags_.auto_hide_tab_bar, + "AutoHideTabBar", + "show tab bar only if multiple windows\n" + + 'You will need to restore the layout after changing (Menu "View/Restore Layout")', + ), + DockFlagWithInfo( + imgui.DockNodeFlags_.no_docking_over_central_node, + "NoDockingInCentralNode", + "prevent docking in central node\n(only works with the main dock space)", + ), + # DockFlagWithInfo(imgui.DockNodeFlags_.passthru_central_node, "PassthruCentralNode", "advanced"), + ] + + main_dock_space_node_flags = ( + hello_imgui.get_runner_params().docking_params.main_dock_space_node_flags + ) + for flag_with_info in all_flags: + _, main_dock_space_node_flags = imgui.checkbox_flags( + flag_with_info.label, main_dock_space_node_flags, flag_with_info.flag + ) + if imgui.is_item_hovered(): + imgui.set_tooltip("%s" % flag_with_info.tip) + + hello_imgui.get_runner_params().docking_params.main_dock_space_node_flags = ( + main_dock_space_node_flags + ) + + +def gui_window_layout_customization(app_state: AppState): + imgui.push_font(app_state.title_font) + imgui.text("Switch between layouts") + imgui.pop_font() + imgui.text('with the menu "View/Layouts"') + if imgui.is_item_hovered(): + imgui.set_tooltip( + "Each layout remembers separately the modifications applied by the user, \n" + + "and the selected layout is restored at startup" + ) + + imgui.separator() + + imgui.push_font(app_state.title_font) + imgui.text("Change the theme") + imgui.pop_font() + imgui.text('with the menu "View/Theme"') + if imgui.is_item_hovered(): + imgui.set_tooltip("The selected theme is remembered and restored at startup") + imgui.separator() + + demo_docking_flags(app_state) + imgui.separator() + + +def gui_window_alternative_theme(app_state: AppState): + # Since this window applies a theme, We need to call "imgui.begin" ourselves so + # that we can apply the theme before opening the window. + # + # In order to obtain this, we applied the following option to the window + # that displays this Gui: + # alternative_theme_window.call_begin_end = False + + # emulate C/C++ static variable: we will store some static variables + # as attributes of the function + statics = gui_window_alternative_theme + + # Apply the theme before opening the window + tweaked_theme = hello_imgui.ImGuiTweakedTheme() + tweaked_theme.theme = hello_imgui.ImGuiTheme_.white_is_white + tweaked_theme.tweaks.rounding = 0.0 + hello_imgui.push_tweaked_theme(tweaked_theme) + + # Open the window + window_opened = imgui.begin("Alternative Theme") + if window_opened: + # Display some widgets + imgui.push_font(app_state.title_font) + imgui.text("Alternative Theme") + imgui.pop_font() + imgui.text("This window uses a different theme") + imgui.set_item_tooltip(""" + tweaked_theme = hello_imgui.ImGuiTheme.ImGuiTweakedTheme() + tweaked_theme.theme = hello_imgui.ImGuiTheme_.white_is_white.value + tweaked_theme.tweaks.rounding = 0.0 + hello_imgui.apply_tweaked_theme(tweaked_theme) + """ + ) + + if imgui.collapsing_header("Basic Widgets", imgui.TreeNodeFlags_.default_open.value): + if not hasattr(statics, "checked"): + statics.checked = True + _, statics.checked = imgui.checkbox("Checkbox", statics.checked) + + if imgui.button("Button"): + hello_imgui.log(hello_imgui.LogLevel.info, "Button was pressed") + imgui.set_item_tooltip("This is a button") + + if not hasattr(statics, "radio"): + statics.radio = 0 + if imgui.radio_button("Radio 1", statics.radio == 0): + statics.radio = 0 + imgui.same_line() + if imgui.radio_button("Radio 2", statics.radio == 1): + statics.radio = 1 + imgui.same_line() + if imgui.radio_button("Radio 3", statics.radio == 2): + statics.radio = 2 + + # Haiku + # Display a image of the haiku below with Japanese characters + # with an informative tooltip + haiku_image_height = hello_imgui.em_size(5.0) + hello_imgui.image_from_asset("images/haiku.png", (0.0, haiku_image_height)) + imgui.set_item_tooltip(""" +Extract from Wikipedia +------------------------------------------------------------------------------- + +In early 1686, Bashō composed one of his best-remembered haiku: + + furu ike ya / kawazu tobikomu / mizu no oto + + an ancient pond / a frog jumps in / the splash of water + +This poem became instantly famous. + +------------------------------------------------------------------------------- + +This haiku is here rendered as an image, mainly to preserve space, +because adding a Japanese font to the project would enlarge its size. +Handling Japanese font is of course possible within ImGui / Hello ImGui! + """) + + # Display the haiku text as an InputTextMultiline + if not hasattr(statics, "poem"): + statics.poem = ( + " Old Pond\n" + " Frog Leaps In\n" + " Water's Sound\n" + "\n" + " Matsuo Bashō - 1686" + ) + + _, statics.poem = imgui.input_text_multiline("##Poem", statics.poem, hello_imgui.em_to_vec2(15.0, 5.5)) + + # a popup with a modal window + if imgui.button("Open Modal"): + imgui.open_popup("MyModal") + popup_opened, _ = imgui.begin_popup_modal("MyModal", None, imgui.WindowFlags_.always_auto_resize.value) + if popup_opened: + imgui.text("This is a modal window") + if imgui.button("Close"): + imgui.close_current_popup() + imgui.end_popup() + + if not hasattr(statics, "text"): + statics.text = "Hello, world!" + _, statics.text = imgui.input_text("Input text", statics.text) + + if imgui.tree_node("Text Display"): + imgui.text("Hello, world!") + imgui.text_colored((1.0, 0.5, 0.5, 1.0), "Some text") + imgui.text_disabled("Disabled text") + imgui.text_wrapped("This is a long text that will be wrapped in the window") + imgui.tree_pop() + + # Close the window + imgui.end() + + # Restore the theme + hello_imgui.pop_tweaked_theme() + + +def demo_assets(app_state: AppState): + imgui.push_font(app_state.title_font) + imgui.text("Image From Assets") + imgui.pop_font() + hello_imgui.begin_group_column() + imgui.dummy(hello_imgui.em_to_vec2(0.0, 0.45)) + imgui.text("Hello") + hello_imgui.end_group_column() + hello_imgui.image_from_asset("images/world.png", hello_imgui.em_to_vec2(2.5, 2.5)) + + +def demo_fonts(app_state: AppState): + imgui.push_font(app_state.title_font) + imgui.text("Fonts - " + icons_fontawesome_6.ICON_FA_ROCKET) + imgui.pop_font() + + imgui.text_wrapped("Mix icons " + icons_fontawesome_6.ICON_FA_FACE_SMILE + " and text " + icons_fontawesome_6.ICON_FA_ROCKET) + if imgui.is_item_hovered(): + imgui.set_tooltip("Example with Font Awesome Icons") + + imgui.text("Emojis") + + with imgui_ctx.begin_group(): + imgui.push_font(app_state.emoji_font) + imgui.text("✌❤🌴🚀") + imgui.pop_font() + + if imgui.is_item_hovered(): + imgui.set_tooltip("Example with NotoEmoji font") + + imgui.text("Colored Fonts") + imgui.push_font(app_state.color_font) + imgui.text("COLOR!") + imgui.pop_font() + if imgui.is_item_hovered(): + imgui.set_tooltip("Example with Playbox-FREE.otf font") + + +def demo_themes(app_state: AppState): + imgui.push_font(app_state.title_font) + imgui.text("Themes") + imgui.pop_font() + + tweaked_theme = hello_imgui.get_runner_params().imgui_window_params.tweaked_theme + + imgui.begin_group() + button_size = hello_imgui.em_to_vec2(7.0, 0.0) + if imgui.button("Cherry", button_size): + tweaked_theme.theme = hello_imgui.ImGuiTheme_.cherry + hello_imgui.apply_tweaked_theme(tweaked_theme) + if imgui.button("DarculaDarker", button_size): + tweaked_theme.theme = hello_imgui.ImGuiTheme_.darcula_darker + hello_imgui.apply_tweaked_theme(tweaked_theme) + imgui.end_group() + if imgui.is_item_hovered(): + imgui.set_tooltip( + "There are lots of other themes: look at the menu View/Theme\n" + "The selected theme is remembered and restored at startup" + ) + + +def gui_window_demo_features(app_state: AppState): + demo_fonts(app_state) + imgui.separator() + demo_assets(app_state) + imgui.separator() + demo_basic_widgets(app_state) + imgui.separator() + demo_rocket(app_state) + imgui.separator() + demo_user_settings(app_state) + imgui.separator() + demo_hide_window(app_state) + imgui.separator() + demo_show_additional_window(app_state) + imgui.separator() + demo_themes(app_state) + imgui.separator() + + +def status_bar_gui(app_state: AppState): + if app_state.rocket_state == RocketState.Preparing: + imgui.text("Rocket completion: ") + imgui.same_line() + imgui.progress_bar(app_state.rocket_progress, hello_imgui.em_to_vec2(7.0, 1.0)) # type: ignore + + +def show_menu_gui(runner_params: hello_imgui.RunnerParams): + hello_imgui.show_app_menu(runner_params) + hello_imgui.show_view_menu(runner_params) + if imgui.begin_menu("My Menu"): + clicked, _ = imgui.menu_item("Test me", "", False) + if clicked: + hello_imgui.log(hello_imgui.LogLevel.warning, "It works") + imgui.end_menu() + + +def show_app_menu_items(): + clicked, _ = imgui.menu_item("A Custom app menu item", "", False) + if clicked: + hello_imgui.log(hello_imgui.LogLevel.info, "Clicked on A Custom app menu item") + + +def show_top_toolbar(app_state: AppState): + imgui.push_font(app_state.large_icon_font) + if imgui.button(icons_fontawesome_6.ICON_FA_POWER_OFF): + hello_imgui.get_runner_params().app_shall_exit = True + + imgui.same_line(imgui.get_window_width() - hello_imgui.em_size(7.0)) + if imgui.button(icons_fontawesome_6.ICON_FA_HOUSE): + hello_imgui.log(hello_imgui.LogLevel.info, "Clicked on Home in the top toolbar") + imgui.same_line() + if imgui.button(icons_fontawesome_6.ICON_FA_FLOPPY_DISK): + hello_imgui.log(hello_imgui.LogLevel.info, "Clicked on Save in the top toolbar") + imgui.same_line() + if imgui.button(icons_fontawesome_6.ICON_FA_ADDRESS_BOOK): + hello_imgui.log(hello_imgui.LogLevel.info, "Clicked on Address Book in the top toolbar") + + imgui.same_line(imgui.get_window_width() - hello_imgui.em_size(2.0)) + imgui.text(icons_fontawesome_6.ICON_FA_BATTERY_THREE_QUARTERS) + imgui.pop_font() + + +def show_right_toolbar(app_state: AppState): + imgui.push_font(app_state.large_icon_font) + if imgui.button(icons_fontawesome_6.ICON_FA_CIRCLE_ARROW_LEFT): + hello_imgui.log(hello_imgui.LogLevel.info, "Clicked on Circle left in the right toolbar") + if imgui.button(icons_fontawesome_6.ICON_FA_CIRCLE_ARROW_RIGHT): + hello_imgui.log(hello_imgui.LogLevel.info, "Clicked on Circle right in the right toolbar") + imgui.pop_font() + + +########################################################################## +# Docking Layouts and Docking windows +########################################################################## + +# +# 1. Define the Docking splits (two versions are available) +# +def create_default_docking_splits() -> List[hello_imgui.DockingSplit]: + # Define the default docking splits, + # i.e. the way the screen space is split in different target zones for the dockable windows + # We want to split "MainDockSpace" (which is provided automatically) into three zones, like this: + # + # ___________________________________________ + # | | | + # | Command| | + # | Space | MainDockSpace | + # |------- | | + # | |--------------------------------| + # | | CommandSpace2 | + # ------------------------------------------- + # | MiscSpace | + # ------------------------------------------- + # + + # Uncomment the next line if you want to always start with this layout. + # Otherwise, modifications to the layout applied by the user layout will be remembered. + # runner_params.docking_params.layout_condition = hello_imgui.DockingLayoutCondition.ApplicationStart + + # Then, add a space named "MiscSpace" whose height is 25% of the app height. + # This will split the preexisting default dockspace "MainDockSpace" in two parts. + split_main_misc = hello_imgui.DockingSplit() + split_main_misc.initial_dock = "MainDockSpace" + split_main_misc.new_dock = "MiscSpace" + split_main_misc.direction = imgui.Dir.down + split_main_misc.ratio = 0.25 + + # Then, add a space to the left which occupies a column whose width is 25% of the app width + split_main_command = hello_imgui.DockingSplit() + split_main_command.initial_dock = "MainDockSpace" + split_main_command.new_dock = "CommandSpace" + split_main_command.direction = imgui.Dir.left + split_main_command.ratio = 0.25 + + # Then, add CommandSpace2 below MainDockSpace + split_main_command2 = hello_imgui.DockingSplit() + split_main_command2.initial_dock = "MainDockSpace" + split_main_command2.new_dock = "CommandSpace2" + split_main_command2.direction = imgui.Dir.down + split_main_command2.ratio = 0.5 + + splits = [split_main_misc, split_main_command, split_main_command2] + return splits + + +def create_alternative_docking_splits() -> List[hello_imgui.DockingSplit]: + # Define alternative docking splits for the "Alternative Layout" + # ___________________________________________ + # | | | + # | Misc | | + # | Space | MainDockSpace | + # | | | + # ------------------------------------------- + # | | | + # | | Command | + # | CommandSpace | Space2 | + # ------------------------------------------- + + split_main_command = hello_imgui.DockingSplit() + split_main_command.initial_dock = "MainDockSpace" + split_main_command.new_dock = "CommandSpace" + split_main_command.direction = imgui.Dir.down + split_main_command.ratio = 0.5 + + split_main_command2 = hello_imgui.DockingSplit() + split_main_command2.initial_dock = "CommandSpace" + split_main_command2.new_dock = "CommandSpace2" + split_main_command2.direction = imgui.Dir.right + split_main_command2.ratio = 0.4 + + split_main_misc = hello_imgui.DockingSplit() + split_main_misc.initial_dock = "MainDockSpace" + split_main_misc.new_dock = "MiscSpace" + split_main_misc.direction = imgui.Dir.left + split_main_misc.ratio = 0.5 + + splits = [split_main_command, split_main_command2, split_main_misc] + return splits + + +# +# 2. Define the Dockable windows +# +def create_dockable_windows(app_state: AppState) -> List[hello_imgui.DockableWindow]: + # A features demo window named "FeaturesDemo" will be placed in "CommandSpace". + # Its Gui is provided by "gui_window_demo_features" + features_demo_window = hello_imgui.DockableWindow() + features_demo_window.label = "Features Demo" + features_demo_window.dock_space_name = "CommandSpace" + features_demo_window.gui_function = lambda: gui_window_demo_features(app_state) + + # A layout customization window will be placed in "MainDockSpace". + # Its Gui is provided by "gui_window_layout_customization" + layout_customization_window = hello_imgui.DockableWindow() + layout_customization_window.label = "Layout customization" + layout_customization_window.dock_space_name = "MainDockSpace" + layout_customization_window.gui_function = lambda: gui_window_layout_customization(app_state) + + # A Log window named "Logs" will be placed in "MiscSpace". It uses the HelloImGui logger gui + logs_window = hello_imgui.DockableWindow() + logs_window.label = "Logs" + logs_window.dock_space_name = "MiscSpace" + logs_window.gui_function = hello_imgui.log_gui + + # A Window named "Dear ImGui Demo" will be placed in "MainDockSpace" + dear_imgui_demo_window = hello_imgui.DockableWindow() + dear_imgui_demo_window.label = "Dear ImGui Demo" + dear_imgui_demo_window.dock_space_name = "MainDockSpace" + dear_imgui_demo_window.imgui_window_flags = imgui.WindowFlags_.menu_bar.value + dear_imgui_demo_window.gui_function = imgui.show_demo_window # type: ignore + + # alternativeThemeWindow + alternative_theme_window = hello_imgui.DockableWindow() + # Since this window applies a theme, We need to call "imgui.begin" ourselves so + # that we can apply the theme before opening the window. + alternative_theme_window.call_begin_end = False + alternative_theme_window.label = "Alternative Theme" + alternative_theme_window.dock_space_name = "CommandSpace2" + alternative_theme_window.gui_function = lambda: gui_window_alternative_theme(app_state) + + dockable_windows = [ + features_demo_window, + layout_customization_window, + logs_window, + dear_imgui_demo_window, + alternative_theme_window, + ] + return dockable_windows + + +# +# 3. Define the layouts: +# A layout is stored inside DockingParams, and stores the splits + the dockable windows. +# Here, we provide the default layout, and two alternative layouts. +def create_default_layout(app_state: AppState) -> hello_imgui.DockingParams: + docking_params = hello_imgui.DockingParams() + # By default, the layout name is already "Default" + # docking_params.layout_name = "Default" + docking_params.docking_splits = create_default_docking_splits() + docking_params.dockable_windows = create_dockable_windows(app_state) + return docking_params + + +def create_alternative_layouts(app_state: AppState) -> List[hello_imgui.DockingParams]: + alternative_layout = hello_imgui.DockingParams() + alternative_layout.layout_name = "Alternative Layout" + alternative_layout.docking_splits = create_alternative_docking_splits() + alternative_layout.dockable_windows = create_dockable_windows(app_state) + + tabs_layout = hello_imgui.DockingParams() + tabs_layout.layout_name = "Tabs Layout" + tabs_layout.dockable_windows = create_dockable_windows(app_state) + # Force all windows to be presented in the MainDockSpace + for window in tabs_layout.dockable_windows: + window.dock_space_name = "MainDockSpace" + # In "Tabs Layout", no split is created + tabs_layout.docking_splits = [] + + return [alternative_layout, tabs_layout] + + +########################################################################## +# Define the app initial theme +########################################################################## +def setup_my_theme(): + """Example of theme customization at App startup + This function is called in the callback `setup_imgui_style` in order to apply a custom theme: + runner_params.callbacks.setup_imgui_style = setup_my_theme() + """ + # Apply default style + hello_imgui.imgui_default_settings.setup_default_imgui_style() + # Create a tweaked theme + tweaked_theme = hello_imgui.ImGuiTweakedTheme() + tweaked_theme.theme = hello_imgui.ImGuiTheme_.material_flat + tweaked_theme.tweaks.rounding = 10.0 + # Apply the tweaked theme + hello_imgui.apply_tweaked_theme(tweaked_theme) # Note: you can also push/pop the theme in order to apply it only to a specific part of the Gui: hello_imgui.push_tweaked_theme(tweaked_theme) / hello_imgui.pop_tweaked_theme() + # Then apply further modifications to ImGui style + imgui.get_style().item_spacing = ImVec2(6, 4) # Reduce spacing between items ((8, 4) by default) + imgui.get_style().set_color_(imgui.Col_.text.value, (0.8, 0.8, 0.85, 1.0)) # Change text color + + +########################################################################## +# main(): here, we simply fill RunnerParams, then run the application +########################################################################## +def main(): + # By default, an assets folder is installed via pip inside site-packages/lg_imgui_bundle/assets + # and provides two fonts (fonts/DroidSans.ttf and fonts/fontawesome-webfont.ttf) + # If you need to add more assets, make a copy of this assets folder and add your own files, + # and call set_assets_folder + hello_imgui.set_assets_folder(demo_utils.demos_assets_folder()) + + # + # Part 1: Define the application state, fill the status and menu bars, and load additional font + # + + # Our application state + app_state = AppState() + + # Hello ImGui params (they hold the settings as well as the Gui callbacks) + runner_params = hello_imgui.RunnerParams() + runner_params.app_window_params.window_title = "Docking Demo" + runner_params.imgui_window_params.menu_app_title = "Docking Demo" + runner_params.app_window_params.window_geometry.size = (1000, 900) + runner_params.app_window_params.restore_previous_geometry = True + runner_params.app_window_params.borderless = True + runner_params.app_window_params.borderless_movable = True + runner_params.app_window_params.borderless_resizable = True + runner_params.app_window_params.borderless_closable = True + + # Set LoadAdditionalFonts callback + runner_params.callbacks.load_additional_fonts = lambda: load_fonts(app_state) + + # + # Status bar + # + # We use the default status bar of Hello ImGui + runner_params.imgui_window_params.show_status_bar = True + # Add custom widgets in the status bar + runner_params.callbacks.show_status = lambda: status_bar_gui(app_state) + # uncomment next line in order to hide the FPS in the status bar + # runner_params.im_gui_window_params.show_status_fps = False + + # + # Menu bar + # + # Here, we fully customize the menu bar: + # by setting `show_menu_bar` to True, and `show_menu_app` and `show_menu_view` to False, + # HelloImGui will display an empty menu bar, which we can fill with our own menu items via the callback `show_menus` + runner_params.imgui_window_params.show_menu_bar = True + runner_params.imgui_window_params.show_menu_app = False + runner_params.imgui_window_params.show_menu_view = False + # Inside `show_menus`, we can call `hello_imgui.show_view_menu` and `hello_imgui.show_app_menu` if desired + runner_params.callbacks.show_menus = lambda: show_menu_gui(runner_params) + # Optional: add items to Hello ImGui default App menu + runner_params.callbacks.show_app_menu_items = show_app_menu_items + + # + # Top and bottom toolbars + # + # toolbar options + edge_toolbar_options = hello_imgui.EdgeToolbarOptions() + edge_toolbar_options.size_em = 2.5 + edge_toolbar_options.window_bg = ImVec4(0.8, 0.8, 0.8, 0.35) + # top toolbar + runner_params.callbacks.add_edge_toolbar( + hello_imgui.EdgeToolbarType.top, + lambda: show_top_toolbar(app_state), + edge_toolbar_options, + ) + # right toolbar + edge_toolbar_options.window_bg.w = 0.4 + runner_params.callbacks.add_edge_toolbar( + hello_imgui.EdgeToolbarType.right, + lambda: show_right_toolbar(app_state), + edge_toolbar_options, + ) + + # + # Load user settings at callbacks `post_init` and save them at `before_exit` + # + runner_params.callbacks.post_init = lambda: load_my_app_settings(app_state) + runner_params.callbacks.before_exit = lambda: save_my_app_settings(app_state) + + # Change style + runner_params.callbacks.setup_imgui_style = setup_my_theme + + # + # Part 2: Define the application layout and windows + # + + # First, tell HelloImGui that we want full screen dock space (this will create "MainDockSpace") + runner_params.imgui_window_params.default_imgui_window_type = ( + hello_imgui.DefaultImGuiWindowType.provide_full_screen_dock_space + ) + # In this demo, we also demonstrate multiple viewports: you can drag windows outside + # out the main window in order to put their content into new native windows + runner_params.imgui_window_params.enable_viewports = True + # Set the default layout (this contains the default DockingSplits and DockableWindows) + runner_params.docking_params = create_default_layout(app_state) + # Add alternative layouts + runner_params.alternative_docking_layouts = create_alternative_layouts(app_state) + + # + # Part 3: Where to save the app settings + # + # tag::app_settings[] + # By default, HelloImGui will save the settings in the current folder. + # This is convenient when developing, but not so much when deploying the app. + # You can tell HelloImGui to save the settings in a specific folder: choose between + # current_folder + # app_user_config_folder + # app_executable_folder + # home_folder + # temp_folder + # documents_folder + # + # Note: app_user_config_folder is: + # AppData under Windows (Example: C:\Users\[Username]\AppData\Roaming) + # ~/.config under Linux + # "~/Library/Application Support" under macOS or iOS + runner_params.ini_folder_type = hello_imgui.IniFolderType.app_user_config_folder + + # runnerParams.ini_filename: this will be the name of the ini file in which the settings + # will be stored. + # In this example, the subdirectory Docking_Demo will be created under the folder defined + # by runnerParams.ini_folder_type. + # + # Note: if ini_filename is left empty, the name of the ini file will be derived + # from app_window_params.window_title + runner_params.ini_filename = "Docking_Demo/Docking_demo.ini" + # end::app_settings[] + + # + # Part 4: Run the app + # + hello_imgui.run(runner_params) + + +if __name__ == "__main__": + main() diff --git a/gui.py b/gui.py index 1afddce..0128e8e 100644 --- a/gui.py +++ b/gui.py @@ -1,54 +1,24 @@ -import imgui +from imgui_bundle import imgui, immapp import glfw import OpenGL.GL as gl -from imgui.integrations.glfw import GlfwRenderer from datatypes import * from view import View -def impl_glfw_init(window_name="Grapher Tool", width=1280, height=720): - if not glfw.init(): - print("Could not initialize OpenGL context") - exit(1) - - # OS X supports only forward-compatible core profiles from 3.2 - glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3) - glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3) - glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE) - - glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, gl.GL_TRUE) - - # Create a windowed mode window and its OpenGL context - window = glfw.create_window(int(width), int(height), window_name, None, None) - glfw.make_context_current(window) - - if not window: - glfw.terminate() - print("Could not initialize Window") - exit(1) - - return window - class GUI(object): def __init__(self): super().__init__() - # Window States - self.window = impl_glfw_init() - gl.glClearColor(*COLOR_BACKGROUND) - imgui.create_context() - self.impl = GlfwRenderer(self.window) - self.io = imgui.get_io() + # self.io = imgui.get_io() # Global GUI Setting - win_w, win_h = glfw.get_window_size(self.window) + '''win_w, win_h = glfw.get_window_size(self.window) fb_w, fb_h = glfw.get_framebuffer_size(self.window) font_scaling_factor = max(float(fb_w) / win_w, float(fb_h) / win_h) font_size_in_pixels = 30 self.io.fonts.add_font_from_file_ttf("assets/MPLUSRounded1c-Regular.ttf", font_size_in_pixels * font_scaling_factor) - self.io.font_global_scale /= font_scaling_factor + self.io.font_global_scale /= font_scaling_factor''' self.view = View() - self.loop() def header(self): imgui.set_next_window_size(io.display_size.x, io.display_size.y*0.03) @@ -62,28 +32,16 @@ class GUI(object): imgui.set_cursor_pos_x((ww - tw) * 0.5) imgui.text("Student Analyzer") - def loop(self): - while not glfw.window_should_close(self.window): - glfw.poll_events() - self.impl.process_inputs() - imgui.new_frame() - + def __call__(self): self.view() - #imgui.show_test_window() - - imgui.render() - - gl.glClearColor(*COLOR_BACKGROUND) - gl.glClear(gl.GL_COLOR_BUFFER_BIT) - - self.impl.render(imgui.get_draw_data()) - glfw.swap_buffers(self.window) - - self.impl.shutdown() - glfw.terminate() if __name__ == "__main__": - - gui = GUI() + immapp.run( + gui_function=GUI(), + window_title="Student Analyzer", + window_size_auto=True, + with_implot=True, + with_markdown=True + ) diff --git a/lecture_editor.py b/lecture_editor.py index b7a2682..235f082 100644 --- a/lecture_editor.py +++ b/lecture_editor.py @@ -1,5 +1,4 @@ -import imgui - +from imgui_bundle import imgui from datatypes import * from model import * diff --git a/main_menu.py b/main_menu.py index 4a1f8f2..94c8a96 100644 --- a/main_menu.py +++ b/main_menu.py @@ -1,4 +1,4 @@ -import imgui +from imgui_bundle import imgui, imgui_ctx from datatypes import * @@ -13,23 +13,23 @@ class MainMenu: if self.new: self.create_new_file() - with imgui.begin_main_menu_bar() as main_menu_bar: + with imgui_ctx.begin_main_menu_bar() as main_menu_bar: if main_menu_bar: - with imgui.begin_menu("File", True) as file_menu: - if file_menu.opened: + with imgui_ctx.begin_menu("File", True) as file_menu: + if file_menu.visible: new, _ = imgui.menu_item("New", " ", False, True) if new: self.new = True imgui.menu_item("Open", " ", False, True) imgui.menu_item("Save", " ", False, True) imgui.menu_item("Save as", " ", False, True) - with imgui.begin_menu("View", True) as view_menu: - if view_menu.opened: - with imgui.begin_menu("Change Layout", True) as open_layout_menu: - if open_layout_menu.opened: + with imgui_ctx.begin_menu("View", True) as view_menu: + if view_menu.visible: + with imgui_ctx.begin_menu("Change Layout", True) as open_layout_menu: + if open_layout_menu.visible: layout_options = list(LayoutOptions) for n, l in enumerate(layout_options): - clicked, _ = imgui.menu_item(l.name.title(), None, False, True) + clicked = imgui.menu_item_simple(l.name.title()) if clicked: return l diff --git a/student_editor.py b/student_editor.py index 48566cb..e4fa3b3 100644 --- a/student_editor.py +++ b/student_editor.py @@ -1,6 +1,5 @@ -import imgui - from model import * +from imgui_bundle import imgui class StudentEditor: def __init__(self): diff --git a/student_graph.py b/student_graph.py index 3c59689..81bd978 100644 --- a/student_graph.py +++ b/student_graph.py @@ -1,5 +1,5 @@ -import imgui import numpy as np +from imgui_bundle import imgui import random from datatypes import * diff --git a/student_info.py b/student_info.py index f64171f..325d695 100644 --- a/student_info.py +++ b/student_info.py @@ -1,60 +1,77 @@ -import imgui from datatypes import * +from imgui_bundle import imgui, imgui_ctx, imgui_md, implot, ImVec2 +import numpy as np from model import * class StudentInfo: def __init__(self): super().__init__() + self.select_class = 0 + self.select_student = 0 - def __call__(self, student: Student): - submissions = Submission.select().where(Submission.student_id == student.id) if student else None + def student_graph(self, student_id: int) -> None: + student = Student.get_by_id(student_id) + clas = Class.get_by_id(student.class_id) + lectures = Lecture.select().where(Lecture.class_id == clas.id) + submissions = Submission.select().where(Submission.student_id == student.id) + + overall_points = np.sum([l.points for l in lectures]) + points = np.sum([sub.points for sub in submissions]) + if points.is_integer(): + points = int(points) - with imgui.begin("Student Info", False, imgui.WINDOW_NO_MOVE | imgui.WINDOW_NO_RESIZE | imgui.WINDOW_NO_COLLAPSE): - if not student: - imgui.text("No Student selected") - return - + subs_data = np.array([sub.points/l.points for sub, l in zip(submissions, lectures)])*100 + subs_labels = [str(l.title) for l in lectures] + + with imgui_ctx.begin_group(): + imgui_md.render(f"# {student.prename} {student.surname}") + imgui_md.render(f"### {clas.name}") + + pb_content = f"{points}/{overall_points} {points/overall_points:.1%}" + imgui.progress_bar(points/overall_points, overlay=pb_content) + + implot.push_colormap(implot.Colormap_.deep.value) + if implot.begin_plot("Performance"): + implot.setup_axes("Lectures", "Percentage") + implot.setup_axes_limits(-1, len(subs_data), 0, 110) + implot.setup_axis_ticks(implot.ImAxis_.x1.value, 0, len(subs_labels), len(subs_labels), subs_labels, False) + implot.plot_bars("Submissions", subs_data) + implot.end_plot() + + def student_list(self) -> int: + classes = Class.select() + content = [f"{n}. {c.name}" for n, c in enumerate(classes, start=1)] + students = Student.select().where(Student.class_id == classes[self.select_class].id) + lectures = Lecture.select().where(Lecture.class_id == classes[self.select_class].id) + + overall_points = np.sum([l.points for l in lectures]) + points = list() + for student in students: + submissions = Submission.select().where(Submission.student_id == student.id) + cummultative = [sub.points for sub in submissions] + passed = np.sum([p > overall_points*0.3 for p in cummultative]) + points.append((student, np.sum(cummultative)/overall_points, passed > 1)) + + students = sorted(points, key=lambda x: x[1], reverse=True) + + with imgui_ctx.begin_group(): + _, self.select_class = imgui.combo("##class_list", self.select_class, content, len(content)) + for n, student in enumerate(students, start=1): + s = student[0] + display = f"{n}. {s.prename} {s.surname}" + _, clicked = imgui.selectable(display, self.select_student == n-1) + if clicked: + self.select_student = n-1 + + return students[self.select_student][0].id + + def __call__(self): + with imgui_ctx.begin("Student Info"): w, h = imgui.get_window_size() - imgui.text_colored(f"{student.prename} {student.surname}", *COLOR_TEXT) - - content = Class.get_by_id(student.class_id).name - text_size = imgui.calc_text_size(content) - imgui.same_line(position=w-1.5*text_size.x) - imgui.text(content) - - if submissions: - overall_points = sum([s.points for s in submissions]) - if overall_points.is_integer(): - overall_points = int(overall_points) - overall_max = sum([lectures.points for lectures in [Lecture.get_by_id(s.lecture_id) for s in submissions]]) - percentile = overall_points / overall_max - imgui.progress_bar(percentile, (w*0.5, h*0.05), f"{overall_points}/{overall_max} {percentile:.1%}") - - content = "Delete" - if submissions: - text_size = imgui.calc_text_size(content) - imgui.same_line(position=w-2*text_size.x) - if imgui.button(content): - # Delete all Submissions related to that Student - #for submission in submissions: - # submission.delete().execute() - # Delete Student - #student.delete().execute() - return - - imgui.separator() - - if not submissions: - imgui.text("No Submission for this Student") - - for n, submission in enumerate(submissions, start=1): - lecture = Lecture.get_by_id(submission.lecture_id) - - points = submission.points - if points.is_integer(): - points = int(points) - - display = f"{n}. {lecture.title} {points}/{lecture.points}" - COLOR = COLOR_TEXT_PASSED if points > lecture.points*0.3 else COLOR_TEXT_FAILED - imgui.text_colored(display, *COLOR) + with imgui_ctx.begin_child("Student Selector", ImVec2(w*0.25, h*0.9)): + id = self.student_list() + imgui.same_line() + with imgui_ctx.begin_child("Student Graph", ImVec2(w*0.7, h*0.9)): + self.student_graph(id) + diff --git a/student_list.py b/student_list.py index 23b2942..7ec48e3 100644 --- a/student_list.py +++ b/student_list.py @@ -1,6 +1,5 @@ -import imgui - from model import * +from imgui_bundle import imgui class StudentList: def __init__(self): diff --git a/student_ranking.py b/student_ranking.py index e8e9e1a..6add0d0 100644 --- a/student_ranking.py +++ b/student_ranking.py @@ -1,5 +1,5 @@ -import imgui import numpy as np +from imgui_bundle import imgui from model import * diff --git a/submission_editor.py b/submission_editor.py index 9a3c728..a231fe5 100644 --- a/submission_editor.py +++ b/submission_editor.py @@ -1,5 +1,4 @@ -import imgui - +from imgui_bundle import imgui from model import * class SubmissionEditor: diff --git a/test.db b/test.db index b29087f..846da44 100644 Binary files a/test.db and b/test.db differ diff --git a/view.py b/view.py index 79e22cd..9e3e55a 100644 --- a/view.py +++ b/view.py @@ -1,84 +1,58 @@ -import imgui +from imgui_bundle import imgui, ImVec2 from datatypes import * from main_menu import MainMenu -from student_editor import StudentEditor -from student_list import StudentList -from student_info import StudentInfo -from class_editor import ClassEditor -from lecture_editor import LectureEditor -from submission_editor import SubmissionEditor +from database_editor import DatabaseEditor -from student_graph import StudentGraph -from student_ranking import StudentRanking +from student_info import StudentInfo def set_layout(size: tuple, pos: tuple) -> None: io = imgui.get_io() - imgui.set_next_window_size( - io.display_size.x*size[0], - io.display_size.y*size[1] - ) - imgui.set_next_window_position( - io.display_size.x*pos[0], - io.display_size.y*pos[1] - ) + size = imgui.ImVec2(*size) + pos = imgui.ImVec2(*pos) + imgui.set_next_window_size(size) + imgui.set_next_window_pos(pos) class GrapherLayout: def __init__(self): super().__init__() - self.student_graph = StudentGraph() - self.student_ranking = StudentRanking() + self.student_info = StudentInfo() + + def set_layout(self): + pass def __call__(self): - set_layout((1, 0.4), (0, 0.02)) - self.student_graph() - - set_layout((0.3, 0.6), (0, 0.42)) - self.student_ranking() + self.student_info() class EditorLayout: def __init__(self): super().__init__() - self.io = imgui.get_io() - - self.student_editor = StudentEditor() - self.student_list = StudentList() - self.student_info = StudentInfo() - self.class_editor = ClassEditor() - self.lecture_editor = LectureEditor() - self.submission_editor = SubmissionEditor() + self.database_editor = DatabaseEditor() + + def set_layout(self): + pass def __call__(self): - set_layout((0.3, 0.3), (0.7, 0.4)) - clas = self.class_editor() - - set_layout((0.5, 0.3), (0.2, 0.4)) - self.student_editor() - - set_layout((0.2, 0.98), (0, 0.02)) - student = self.student_list(clas) - - set_layout((0.3, 0.38), (0.7, 0.02)) - self.student_info(student) - - set_layout((0.5, 0.3), (0.2, 0.7)) - self.lecture_editor(clas) - - set_layout((0.5, 0.3), (0.2, 0.1)) - self.submission_editor(clas) + self.database_editor() class View: def __init__(self): super().__init__() - self.io = imgui.get_io() self.current = LayoutOptions.GRAPHER self.main_menu = MainMenu() self.editor = EditorLayout() self.grapher = GrapherLayout() + def switch_context(self, ctx: LayoutOptions) -> None: + match ctx: + case LayoutOptions.EDITOR: + self.editor.set_layout() + case LayoutOptions.GRAPHER: + self.grapher.set_layout() + def __call__(self): option = self.main_menu() if option: