From c382aac84d6763694663f91346272ce8e546478a Mon Sep 17 00:00:00 2001 From: DerGrumpf Date: Fri, 24 Jan 2025 12:33:03 +0100 Subject: [PATCH] Changed: Complete Overall --- analyzer.py | 436 +++++++++++++++ appstate.py | 49 ++ assets/Student_list.csv | 69 +-- test.db => assets/WiSe_24_25.db | Bin 49152 -> 53248 bytes assets/convert.py | 13 +- assets/icons/db_icon.png | Bin 0 -> 4921 bytes class_editor.py | 35 -- database.py | 223 ++++++++ datatypes.py | 15 - demo_docking.py | 926 -------------------------------- gui.py | 127 +++-- lecture_editor.py | 44 -- main_menu.py | 37 -- model.py | 6 +- state | Bin 0 -> 16384 bytes student_editor.py | 41 -- student_graph.py | 78 --- student_info.py | 77 --- student_list.py | 27 - student_ranking.py | 37 -- submission_editor.py | 66 --- view.py | 65 --- 22 files changed, 844 insertions(+), 1527 deletions(-) create mode 100644 analyzer.py create mode 100644 appstate.py rename test.db => assets/WiSe_24_25.db (57%) create mode 100644 assets/icons/db_icon.png delete mode 100644 class_editor.py create mode 100644 database.py delete mode 100644 datatypes.py delete mode 100644 demo_docking.py delete mode 100644 lecture_editor.py delete mode 100644 main_menu.py create mode 100644 state delete mode 100644 student_editor.py delete mode 100644 student_graph.py delete mode 100644 student_info.py delete mode 100644 student_list.py delete mode 100644 student_ranking.py delete mode 100644 submission_editor.py delete mode 100644 view.py diff --git a/analyzer.py b/analyzer.py new file mode 100644 index 0000000..4632316 --- /dev/null +++ b/analyzer.py @@ -0,0 +1,436 @@ +# Custom +from model import * +from appstate import AppState + +# External +from imgui_bundle import ( + imgui, + immapp, + hello_imgui, + imgui_md, + implot, + implot3d, + immvision, + ImVec2, + ImVec4, + im_file_dialog +) +from PIL import ImageColor +import numpy as np +from numpy.typing import NDArray + +# Built In +from typing import List, Any + +def student_list(app_state: AppState) -> None: + statics = student_list + if not app_state.current_class_id: + imgui.text("No Class found in Database") + return + + if not hasattr(statics, "select"): + statics.select = 0 + + statics.students = Student.select().where(Student.class_id == app_state.current_class_id) + statics.students = statics.students if statics.students else None + if not statics.students: + imgui.text(f"No Stundents found in {Class.get_by_id(app_state.current_class_id).name}") + return + + for n, student in enumerate(statics.students, start=1): + display = f"{n}. {student.prename} {student.surname}" + _, clicked = imgui.selectable(display, statics.select == n-1) + if clicked: + statics.select = n-1 + + app_state.current_student_id = statics.students[statics.select].id + +def lecture_list(app_state: AppState) -> None: + statics = lecture_list + + if not app_state.current_class_id: + imgui.text("No class found in Database") + return + + lectures = Lecture.select().where(Lecture.class_id == app_state.current_class_id) + + if not lectures: + imgui.text(f"No Lectures found for {Class.get_by_id(app_state.current_class_id).name}") + return + + if not hasattr(statics, "select"): + statics.select = 0 + + for n, lecture in enumerate(lectures, start=1): + display = f"{n}. {lecture.title}" + _, clicked = imgui.selectable(display, statics.select == n-1) + if clicked: + statics.select = n-1 + + app_state.current_lecture_id = lectures[statics.select].id + +def class_list(app_state: AppState) -> None: + statics = class_list + + if db.is_closed(): + imgui.text("No Database loaded") + return + + classes = Class.select() if db else None + if not classes: + imgui.text("No Classes currently in Database") + return + + if not hasattr(statics, "select"): + statics.select = 0 + + for n, clas in enumerate(classes, start=1): + display = f"{n}. {clas.name}" + _, clicked = imgui.selectable(display, statics.select == n-1) + if clicked: + statics.select = n-1 + + app_state.current_class_id = classes[statics.select].id + +def submissions_list(app_state: AppState) -> None: + statics = submissions_list + + if not app_state.current_lecture_id: + imgui.text("No Lecture found") + return + if not app_state.current_student_id: + imgui.text("No Student found") + return + + submissions = Submission.select().where(Submission.lecture_id == app_state.current_lecture_id and Submission.student_id == app_state.current_student_id) + + if not submissions: + student = Student.get_by_id(app_state.current_student_id) + lecture = Lecture.get_by_id(app_state.current_lecture_id) + imgui.text(f"{student.prename} {student.surname} didn't submitted for {lecture.title}") + return + + if not hasattr(statics, "select"): + statics.select = 0 + + for n, sub in enumerate(submissions, start=1): + lecture = Lecture.get_by_id(sub.lecture_id) + points = sub.points + if points.is_integer(): + points = int(points) + display = f"{n}. {lecture.title} {points}/{lecture.points}" + _, clicked = imgui.selectable(display, statics.select == n-1) + if clicked: + statics.select = n-1 + + app_state.current_submission_id = submissions[statics.select].id + +def plot_bar_line_percentage(data: np.array, labels: list, avg: float) -> None: + if not data.size > 0: + imgui.text("No Data available") + return + + name = hash(avg) + + if avg.is_integer(): + avg = int(avg) + avg = np.ones(len(data)) * avg + + w, h = imgui.get_window_size() + implot.push_colormap(implot.Colormap_.hot.value) + if implot.begin_plot(f"Performance##{name}", ImVec2(-1, h*0.4), implot.Flags_.no_mouse_text.value | implot.Flags_.no_inputs.value): + implot.setup_axes("Lectures", "Percentage") + implot.setup_axes_limits(-1, len(data), 0, 110) + implot.push_style_var(implot.StyleVar_.fill_alpha.value, 0.6) + implot.push_style_var(implot.StyleVar_.line_weight.value, 3) + implot.setup_axis_ticks(implot.ImAxis_.x1.value, 0, len(labels), len(labels), [" " for _ in labels], False) + implot.plot_bars("Submissions", data) + implot.plot_line("Average", avg) + + implot.push_style_color(implot.Col_.inlay_text, ImVec4(190,190,40,255)/255) + for x_pos, label in enumerate(labels): + y_pos = 50 + implot.plot_text(label, x_pos, y_pos//2, ImVec2(0,0), implot.TextFlags_.vertical.value) + implot.pop_style_color() + + implot.pop_style_var() + implot.end_plot() + +COLOR_TEXT_PASSED = tuple([e/255 for e in ImageColor.getcolor("#1AFE49","RGBA")]) +COLOR_TEXT_FAILED = tuple([e/255 for e in ImageColor.getcolor("#FF124F","RGBA")]) +@immapp.static(inited=False) +def student_graph(app_state: AppState) -> None: + statics = student_graph + if not statics.inited: + statics.id = -1 + statics.student = None + statics.lectures = None + statics.points = None + statics.sub_points = None + statics.subs_data = None + statics.subs_labels = None + statics.max_points = None + statics.avg = None + statics.inited = True + + if id != app_state.current_student_id: + statics.id = app_state.current_student_id + + if not app_state.current_student_id: + imgui.text("No Students in Database") + return + statics.student = Student.get_by_id(app_state.current_student_id) + submissions = Submission.select().where(Submission.student_id == statics.student.id) + + statics.lectures = [Lecture.get_by_id(sub.lecture_id) for sub in submissions] + statics.max_points = np.sum([l.points for l in statics.lectures]) + statics.sub_points = [sub.points for sub in submissions] + statics.points = np.sum(statics.sub_points) + if statics.points.is_integer(): + statics.points = int(statics.points) + + statics.subs_data = np.array([p/mp.points for p, mp in zip(statics.sub_points, statics.lectures)], dtype=np.float32)*100 + statics.subs_labels = [f"{l.title} {int(points) if points.is_integer() else points}/{l.points}" for l, points in zip(statics.lectures, statics.sub_points)] + statics.avg = statics.points/statics.max_points*100 + + w, h = imgui.get_window_size() + imgui_md.render(f"# {statics.student.prename} {statics.student.surname}") + imgui_md.render(f"### {statics.points}/{statics.max_points}") + imgui.text(" ") + imgui.progress_bar(statics.points/statics.max_points, ImVec2(w*0.9, h*0.05), f"{statics.points}/{statics.max_points} {statics.points/statics.max_points:.1%}") + plot_bar_line_percentage(statics.subs_data, statics.subs_labels, statics.avg) + imgui.separator() + for n, data in enumerate(zip(statics.lectures, statics.sub_points), start=1): + lecture, points = data + COLOR = COLOR_TEXT_PASSED if points >= lecture.points*0.3 else COLOR_TEXT_FAILED + imgui.text_colored(COLOR, f"{n}. {lecture.title}") + +@immapp.static(inited=False) +def sex_graph(app_state: AppState) -> None: + statics = sex_graph + + if db.is_closed(): + imgui.text("No Database loaded") + return + + if not statics.inited: + statics.max_points = None + statics.male_points = None + statics.female_points = None + statics.male_percentage = None + statics.female_percentage = None + statics.male_labels = None + statics.female_labels = None + statics.lectures = None + statics.class_id = -1 + statics.state = 0 + statics.inited = True + + if not app_state.current_class_id: + imgui.text("No Class found") + return + + if statics.class_id != app_state.current_class_id: + statics.class_id = app_state.current_class_id + lectures = Lecture.select().where(Lecture.class_id == statics.class_id) + statics.lectures = lectures + + statics.max_points = np.empty(len(lectures)) + statics.male_points = np.empty(len(lectures)) + statics.female_points = np.empty(len(lectures)) + for n, lecture in enumerate(lectures): + statics.max_points[n] = lecture.points # Acc points + + m_count = 0 + f_count = 0 + m_points = 0 + f_points = 0 + + for sub in Submission.select().where(Submission.lecture_id == lecture.id): + if Student.get_by_id(sub.student_id).sex == 'Male': + m_points += sub.points + m_count += 1 + else: + f_points += sub.points + f_count += 1 + statics.male_points[n] = m_points/m_count/lecture.points + statics.female_points[n] = f_points/f_count/lecture.points + + statics.male_percentage = np.sum(statics.male_points)/len(statics.male_points) + statics.female_percentage = np.sum(statics.female_points)/len(statics.female_points) + + statics.male_labels = [f"{l.title} | {points:.1%}" for l, points in zip(lectures, statics.male_points)] + statics.female_labels = [f"{l.title} | {points:.1%}" for l, points in zip(lectures, statics.female_points)] + + w, h = imgui.get_window_size() + if statics.state == 0: + imgui_md.render("# Male") + imgui.progress_bar(statics.male_percentage, ImVec2(w*0.9, h*0.05), f"{statics.male_percentage:.1%} n={len(Student.select().where(Student.sex == 'Male'))}") + plot_bar_line_percentage(statics.male_points*100, statics.male_labels, statics.male_percentage*100) + + imgui_md.render("# Female") + imgui.progress_bar(statics.female_percentage, ImVec2(w*0.9, h*0.05), f"{statics.female_percentage:.1%} n={len(Student.select().where(Student.sex == 'Female'))}") + plot_bar_line_percentage(statics.female_points*100, statics.female_labels, statics.female_percentage*100) + + if statics.state == 1: + male_xs = np.arange(1, len(statics.male_points)+1, dtype=np.float32) + male_ys = male_xs*0 + male_zs = np.array(statics.male_points*100, dtype=np.float32) + + female_xs = np.arange(1, len(statics.female_points)+1, dtype=np.float32) + female_ys = np.ones(len(statics.female_points), dtype=np.float32) + female_zs = np.array(statics.female_points*100, dtype=np.float32) + + if implot3d.begin_plot("3D Gender Plot", ImVec2(w*0.9, h*0.9)): + implot3d.setup_axes("Lecture", "Gender", "Percentage") + implot3d.setup_box_scale(1.1, 1.1, 1.1) + implot3d.setup_axes_limits(0, len(statics.male_points)+1, -0.2, 1.2, 0, 110) + + implot3d.set_next_marker_style(implot3d.Marker_.square.value) + implot3d.plot_line("Male", male_xs, male_ys, male_zs) + + implot3d.set_next_marker_style(implot3d.Marker_.circle.value) + implot3d.plot_line("Female", female_xs, female_ys, female_zs) + + for n, l in enumerate(statics.lectures, start=1): + implot3d.plot_text(l.title, n, np.clip(n/len(statics.lectures)), 50, np.pi/4) + + implot3d.end_plot() + + if imgui.button("Change 2D/3D"): + statics.state = not statics.state + + + +COLOR_TEXT_FIRST = tuple([e/255 for e in ImageColor.getcolor("#FFCF40","RGBA")]) +COLOR_TEXT_SECOND = tuple([e/255 for e in ImageColor.getcolor("#A5A9B4","RGBA")]) +COLOR_TEXT_THIRD = tuple([e/255 for e in ImageColor.getcolor("#97564A","RGBA")]) +COLOR_TEXT_ELSE = tuple([e/255 for e in ImageColor.getcolor("#85EBD9","RGBA")]) +@immapp.static(inited=False) +def ranking(app_state: AppState) -> None: + statics = ranking + + if not statics.inited: + statics.id = -1 + statics.rank = None + statics.state = 1 + statics.inited = True + + imgui_md.render("# Student Ranking") + + if not app_state.current_class_id: + imgui.text("No class selected") + return + + if statics.id != app_state.current_class_id: + statics.id = app_state.current_class_id + + students = Student.select().where(Student.class_id == statics.id) + max_points = np.sum([l.points for l in Lecture.select().where(Lecture.class_id == statics.id)]) + + statics.rank = list() + for student in students: + points = list() + for sub in Submission.select().where(Submission.student_id == student.id): + points.append(sub.points) + statics.rank.append((student, sum(points)/max_points)) + statics.rank = sorted(statics.rank, key=lambda item: item[1], reverse=True) + + if statics.state == 0: + for n, data in enumerate(statics.rank, start=1): + student, points = data + display = f"{n}. {student.prename} {student.surname} {points:.1%}" + COLOR = COLOR_TEXT_ELSE + if n == 1: + COLOR = COLOR_TEXT_FIRST + if n == 2: + COLOR = COLOR_TEXT_SECOND + if n == 3: + COLOR = COLOR_TEXT_THIRD + imgui.text_colored(COLOR, display) + + if statics.state == 1: + w, h = imgui.get_window_size() + if implot.begin_plot("Ranking", ImVec2(w*0.9, h*0.9)): + implot.plot_bars("Ranking", np.array([p[1] for p in reversed(statics.rank)])*100, 0.67, 0, implot.BarsFlags_.horizontal.value) + for n, s in enumerate(reversed(statics.rank)): + student = s[0] + implot.plot_text(f"{student.prename} {student.surname}", s[1]*50, n) + implot.end_plot() + + if imgui.button("Change"): + statics.state = not statics.state + +def analyzer_docking_splits() -> List[hello_imgui.DockingSplit]: + 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.2 + + # 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.25 + + splits = [split_main_misc, split_main_command, split_main_command2] + return splits + +def set_analyzer_layout(app_state: AppState) -> List[hello_imgui.DockableWindow]: + student_selector = hello_imgui.DockableWindow() + student_selector.label = "Students" + student_selector.dock_space_name = "CommandSpace" + student_selector.gui_function = lambda: student_list(app_state) + + lecture_selector = hello_imgui.DockableWindow() + lecture_selector.label = "Lectures" + lecture_selector.dock_space_name = "CommandSpace2" + lecture_selector.gui_function = lambda: lecture_list(app_state) + + class_selector = hello_imgui.DockableWindow() + class_selector.label = "Classes" + class_selector.dock_space_name = "CommandSpace2" + class_selector.gui_function = lambda: class_list(app_state) + + submission_selector = hello_imgui.DockableWindow() + submission_selector.label = "Submissions" + submission_selector.dock_space_name = "CommandSpace" + submission_selector.gui_function = lambda: submissions_list(app_state) + + student_info = hello_imgui.DockableWindow() + student_info.label = "Student Analyzer" + student_info.dock_space_name = "MainDockSpace" + student_info.gui_function = lambda: student_graph(app_state) + + sex_info = hello_imgui.DockableWindow() + sex_info.label = "Analyze by Gender" + sex_info.dock_space_name = "MainDockSpace" + sex_info.gui_function = lambda: sex_graph(app_state) + + student_ranking = hello_imgui.DockableWindow() + student_ranking.label = "Ranking" + student_ranking.dock_space_name = "MainDockSpace" + student_ranking.gui_function = lambda: ranking(app_state) + + return [ + class_selector, student_selector, + lecture_selector, submission_selector, + student_info, sex_info, + student_ranking + ] + +def analyzer_layout(app_state: AppState) -> hello_imgui.DockingParams: + docking_params = hello_imgui.DockingParams() + docking_params.layout_name = "Analyzer" + docking_params.docking_splits = analyzer_docking_splits() + docking_params.dockable_windows = set_analyzer_layout(app_state) + return docking_params diff --git a/appstate.py b/appstate.py new file mode 100644 index 0000000..31979ca --- /dev/null +++ b/appstate.py @@ -0,0 +1,49 @@ +from imgui_bundle import hello_imgui +from model import * +from datetime import datetime + +class AppState: + current_class_id: int + current_lecture_id: int + current_student_id: int + current_submission_id: int + + def __init__(self): + self.current_class_id = None + self.current_lecture_id = None + self.current_student_id = None + self.current_submission_id = None + + def update(self): + + clas = Class.select() + self.current_class_id = clas[0].id if clas else None + + lectures = Lecture.select().where(Lecture.class_id == self.current_class_id) + self.current_lecture_id = lectures[0].id if lectures else None + + students = Student.select().where(Student.class_id == self.current_class_id) + self.current_student_id = students[0].id if students else None + + submissions = Submission.select().where(Submission.lecture_id == self.current_lecture_id and Submission.student_id == self.current_student_id) + self.current_submission_id = submissions[0].id if submissions else None + + LOG_DEBUG(f"Updated App State {repr(self)}") + + def __repr__(self): + return f''' + Class ID: {self.current_class_id} + Lecture ID: {self.current_lecture_id} + Student ID: {self.current_student_id} + Submission ID: {self.current_submission_id} + ''' + +def log(log_level: hello_imgui.LogLevel, msg: str) -> None: + time = datetime.now().strftime("%X") + hello_imgui.log(log_level, f"[{time}] {msg}") + +LOG_DEBUG = lambda msg: log(hello_imgui.LogLevel.debug, msg) +LOG_INFO = lambda msg: log(hello_imgui.LogLevel.info, msg) +LOG_WARNING = lambda msg: log(hello_imgui.LogLevel.warning, msg) +LOG_ERROR = lambda msg: log(hello_imgui.LogLevel.error, msg) + diff --git a/assets/Student_list.csv b/assets/Student_list.csv index 064b71e..6765eaf 100644 --- a/assets/Student_list.csv +++ b/assets/Student_list.csv @@ -1,34 +1,35 @@ -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 -Nele,Grundke,Female,23.5,13,16,28,20,17,21,18 -Anna,Grünewald,Female,12,14,16,29,16,15,19,9 -Yannik,Haupt,Male,18,6,14,21,13,2,9,0 -Janna,Heiny,Female,30,15,18,33,18,20,22,25 -Milena,Krieger,Female,30,15,18,33,20,20,21.5,26 -Julia,Limbach,Female,27.5,12,18,29,11,19,17.5,26 -Viktoria,Litza,Female,21.5,15,18,27,13,20,22,21 -Manthey,Leonie,Female,28.5,14,18,29,20,10,18,23 -Izabel,Mike,Female,29.5,15,15,35,11,4,19,21 -Lea,Noglik,Female,22.5,15,17,34,13,10,20,0 -Donika,Nuhiu,Female,31,13.5,18,35,14,10,17,18 -Julia,Renner,Female,27.5,10,14,0,20,17,11,20 -Fabian,Rothberger,Male,30.5,15,18,34,17,17,19,22 -Natascha,Rott,Female,29.5,12,18,32,19,20,21,26 -Isabel,Rudolf,Female,27.5,9,17,34,16,19,19,21 -Melina,Sablotny,Female,31,15,18,33,20,20,21,19 -Alea,Schleier,Female,27,14,18,34,16,18,21.5,22 -Flemming,Schur,Male,29.5,15,17,34,19,20,19,22 -Marie,Seeger,Female,27.5,15,18,32,14,9,17,22 -Lucy,Thiele,Female,27.5,15,18,27,20,17,19,18 -Lara,Troschke,Female,28.5,14,17,28,13,19,21,25 -Inga-Brit,Turschner,Female,25.5,14,18,34,20,16,19,22 -Alea,Unger,Female,30,12,18,31,20,20,21,22 -Marie,Wallbaum,Female,28.5,14,18,34,17,20,19,24 -Katharina,Walz,Female,31,15,18,31,19,19,17,24 -Xiaowei,Wang,Male,30.5,14,18,26,19,17,0,0 -Lilly-Lu,Warnken,Female,30,15,18,30,14,17,19,14 +First Name,Last Name,Sex,Tutorial 1,Tutorial 2,Extended Applications,Numpy & MatPlotLib,SciPy,Monte Carlo,Pandas & Seaborn,Folium,Statistical Test Methods,Data Analysis +Abdalaziz,Abunjaila,Male,30.5,15,18,28,17,17,17,22,0,0 +Marleen,Adolphi,Female,29.5,15,18,32,19,20,17,24,23,0 +Sarina,Apel,Female,28.5,15,18,32,20,20,21,24,20,0 +Skofiare,Berisha,Female,29.5,13,18,34,20,17,20,26,16,0 +Aurela,Brahimi,Female,17.5,15,15.5,26,16,17,19,16,0,0 +Cam Thu,Do,Female,31,15,18,34,19,20,21.5,22,12,0 +Nova,Eib,Female,31,15,15,34,20,20,21,27,19,0 +Nele,Grundke,Female,23.5,13,16,28,20,17,21,18,22,0 +Anna,Grünewald,Female,12,14,16,29,16,15,19,9,0,0 +Yannik,Haupt,Male,18,6,14,21,13,2,9,0,0,0 +Janna,Heiny,Female,30,15,18,33,18,20,22,25,24,30 +Milena,Krieger,Female,30,15,18,33,20,20,21.5,26,20,0 +Julia,Limbach,Female,27.5,12,18,29,11,19,17.5,26,24,0 +Viktoria,Litza,Female,21.5,15,18,27,13,20,22,21,21,0 +Leonie,Manthey,Female,28.5,14,18,29,20,10,18,23,16,28 +Izabel,Mike,Female,29.5,15,15,35,11,15,19,21,21,27 +Lea,Noglik,Female,22.5,15,17,34,13,10,20,21,19,0 +Donika,Nuhiu,Female,31,13.5,18,35,14,10,17,18,19,6 +Julia,Renner,Female,27.5,10,14,32,20,17,11,20,24,0 +Fabian,Rothberger,Male,30.5,15,18,34,17,17,19,22,18,0 +Natascha,Rott,Female,29.5,12,18,32,19,20,21,26,23,0 +Isabel,Rudolf,Female,27.5,9,17,34,16,19,19,21,16,0 +Melina,Sablotny,Female,31,15,18,33,20,20,21,19,11,0 +Alea,Schleier,Female,27,14,18,34,16,18,21.5,22,15,22 +Flemming,Schur,Male,29.5,15,17,34,19,20,19,22,18,0 +Marie,Seeger,Female,27.5,15,18,32,14,9,17,22,9,0 +Lucy,Thiele,Female,27.5,15,18,27,20,17,19,18,22,0 +Lara,Troschke,Female,28.5,14,17,28,13,19,21,25,12,0 +Inga-Brit,Turschner,Female,25.5,14,18,34,20,16,19,22,17,0 +Alea,Unger,Female,30,12,18,31,20,20,21,22,15,21.5 +Marie,Wallbaum,Female,28.5,14,18,34,17,20,19,24,12,0 +Katharina,Walz,Female,31,15,18,31,19,19,17,24,17,14.5 +Xiaowei,Wang,Male,30.5,14,18,26,19,17,0,0,0,0 +Lilly-Lu,Warnken,Female,30,15,18,30,14,17,19,14,16,0 diff --git a/test.db b/assets/WiSe_24_25.db similarity index 57% rename from test.db rename to assets/WiSe_24_25.db index 846da4461b5795d5ba595aa15316d411084d08aa..cd99f60e5a426d14947ec76917beb9848ed00757 100644 GIT binary patch literal 53248 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 literal 49152 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 literal 0 HcmV?d00001 diff --git a/class_editor.py b/class_editor.py deleted file mode 100644 index 1751ea3..0000000 --- a/class_editor.py +++ /dev/null @@ -1,35 +0,0 @@ -from imgui_bundle import imgui, imgui_ctx -from model import * - -class ClassEditor: - def __init__(self): - super().__init__() - self.add_name = str() - self.select = 0 - - def __call__(self): - classes = Class.select() - - with imgui_ctx.begin("Class 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/database.py b/database.py new file mode 100644 index 0000000..335c0a9 --- /dev/null +++ b/database.py @@ -0,0 +1,223 @@ +# Custom +from model import * +from appstate import * + +# External +from imgui_bundle import ( + imgui, + imgui_ctx, + immapp, + imgui_md, + im_file_dialog, + hello_imgui +) + +# Built In +from typing import List +import shelve +from pathlib import Path +from datetime import datetime + +def file_info(path: Path) -> None: + stat = path.stat() + modified = datetime.fromtimestamp(stat.st_atime) + created = datetime.fromtimestamp(stat.st_ctime) + format = '%c' + + data = { + "File": path.name, + "Size": f"{stat.st_size/100} KB", + "Modified": modified.strftime(format), + "Created": created.strftime(format) + } + + if imgui.begin_table("File Info", 2): + imgui.table_setup_column(" ", 0) + imgui.table_setup_column(" ") + + for k, v in data.items(): + imgui.push_id(k) + imgui.table_next_row() + imgui.table_next_column() + imgui.text(k) + imgui.table_next_column() + imgui.text(v) + imgui.pop_id() + imgui.end_table() + +@immapp.static(inited=False, res=False) +def select_file(app_state: AppState): + statics = select_file + if not statics.inited: + statics.res = None + statics.current = None + with shelve.open("state") as state: + statics.current = Path(state["DB"]) + statics.inited = True + + imgui_md.render("# Database Manager") + file_info(statics.current) + + if imgui.button("Open File"): + im_file_dialog.FileDialog.instance().open("SelectDatabase", "Open Database", "Database File (*.json;*.db){.json,.db}") + if im_file_dialog.FileDialog.instance().is_done("SelectDatabase"): + if im_file_dialog.FileDialog.instance().has_result(): + statics.res = im_file_dialog.FileDialog.instance().get_result() + LOG_INFO(f"Load File {statics.res}") + im_file_dialog.FileDialog.instance().close() + + if statics.res: + filename = statics.res.filename() + info = Path(statics.res.path()) + + imgui.separator() + file_info(info) + + file = None + if imgui.button("Load"): + if not db.is_closed(): + db.close() + + if statics.res.extension() == '.json': + file = filename.removesuffix('.json') + file = file + '.db' + db.init(file) + db.connect(reuse_if_open=True) + db.create_tables([Class, Student, Lecture, Submission]) + load_from_json(str(info)) + LOG_INFO(f"Successfully created {file}") + + if statics.res.extension() == '.db': + file = str(statics.res.path()) + db.init(file) + db.connect(reuse_if_open=True) + db.create_tables([Class, Student, Lecture, Submission]) + LOG_INFO(f"Successfully loaded {filename}") + + with shelve.open("state") as state: + state["DB"] = file + app_state.update() + statics.res = None + +@immapp.static(inited=False) +def table(app_state: AppState) -> None: + statics = table + if not statics.inited: + statics.class_id = None + statics.lectures = None + statics.points = list() + statics.inited = True + + if statics.class_id != app_state.current_class_id: + statics.class_id = app_state.current_class_id + statics.lectures = Lecture.select().where(Lecture.class_id == statics.class_id) + statics.data = dict() + for student in Student.select().where(Student.class_id == statics.class_id): + subs = Submission.select().where(Submission.student_id == student.id) + points = [sub.points for sub in subs] + statics.data[f"{student.prename} {student.surname}"] = points + + if not statics.lectures: + imgui.text("No Lecture queried") + return + + table_flags = ( + imgui.TableFlags_.row_bg.value + | imgui.TableFlags_.borders.value + | imgui.TableFlags_.resizable.value + | imgui.TableFlags_.sizing_stretch_same.value + ) + + if imgui.begin_table("Overview", len(statics.lectures)+1, table_flags): + imgui.table_setup_column("Students") + for n, lecture in enumerate(statics.lectures, start=1): + imgui.table_setup_column(f"{n}. {lecture.title} ({lecture.points})") + imgui.table_setup_scroll_freeze(1, 1) + imgui.table_headers_row() + + for k, v in statics.data.items(): + imgui.push_id(k) + imgui.table_next_row() + imgui.table_next_column() + imgui.text(k) + for points in v: + imgui.table_next_column() + if points.is_integer(): + points = int(points) + imgui.text(str(points)) + imgui.pop_id() + + imgui.end_table() + +@immapp.static(inited=False) +def class_editor() -> None: + statics = class_editor + if not statics.inited: + statics.classes = None + statics.selected = 0 + statics.inited = True + + statics.classes = Class.select() + + imgui_md.render("# Edit Classes") + _, statics.selected = imgui.combo("Classes", statics.selected, [c.name for c in statics.classes]) + imgui.text(statics.classes[statics.selected].name) + +def database_editor(app_state: AppState) -> None: + class_editor() + +def database_docking_splits() -> List[hello_imgui.DockingSplit]: + 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.3 + + # Log Space + 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.3 + + 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.2 + + splits = [split_main_misc, split_main_command, split_main_command2] + return splits + +def set_database_editor_layout(app_state: AppState) -> List[hello_imgui.DockableWindow]: + + file_dialog = hello_imgui.DockableWindow() + file_dialog.label = "Database" + file_dialog.dock_space_name = "MiscSpace" + file_dialog.gui_function = lambda: select_file(app_state) + + log = hello_imgui.DockableWindow() + log.label = "Logs" + log.dock_space_name = "CommandSpace2" + log.gui_function = hello_imgui.log_gui + + table_view = hello_imgui.DockableWindow() + table_view.label = "Table" + table_view.dock_space_name = "MainDockSpace" + table_view.gui_function = lambda: table(app_state) + + editor = hello_imgui.DockableWindow() + editor.label = "Editor" + editor.dock_space_name = "CommandSpace" + editor.gui_function = lambda: database_editor(app_state) + + return [ + file_dialog, log, table_view, editor + ] + +def database_editor_layout(app_state: AppState) -> hello_imgui.DockingParams: + docking_params = hello_imgui.DockingParams() + docking_params.layout_name = "Database Editor" + docking_params.docking_splits = database_docking_splits() + docking_params.dockable_windows = set_database_editor_layout(app_state) + return docking_params diff --git a/datatypes.py b/datatypes.py deleted file mode 100644 index 74eb1be..0000000 --- a/datatypes.py +++ /dev/null @@ -1,15 +0,0 @@ -from PIL import ImageColor -from enum import IntEnum - -# Global Color Pallet -COLOR_BACKGROUND = tuple([e/255 for e in ImageColor.getcolor("#29132E","RGBA")]) -COLOR_1 = tuple([e/255 for e in ImageColor.getcolor("#321450","RGBA")]) -COLOR_2 = tuple([e/255 for e in ImageColor.getcolor("#860029","RGBA")]) -COLOR_3 = tuple([e/255 for e in ImageColor.getcolor("#DE004E","RGBA")]) -COLOR_TEXT = tuple([e/255 for e in ImageColor.getcolor("#F887FF","RGBA")]) -COLOR_TEXT_PASSED = tuple([e/255 for e in ImageColor.getcolor("#1AFE49","RGBA")]) -COLOR_TEXT_FAILED = tuple([e/255 for e in ImageColor.getcolor("#FF124F","RGBA")]) - -class LayoutOptions(IntEnum): - EDITOR = 1 - GRAPHER = 2 diff --git a/demo_docking.py b/demo_docking.py deleted file mode 100644 index 1be190e..0000000 --- a/demo_docking.py +++ /dev/null @@ -1,926 +0,0 @@ -# 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 0128e8e..1565dd5 100644 --- a/gui.py +++ b/gui.py @@ -1,47 +1,96 @@ -from imgui_bundle import imgui, immapp -import glfw -import OpenGL.GL as gl -from datatypes import * -from view import View +# Custom +from model import * +from appstate import AppState -class GUI(object): - def __init__(self): - super().__init__() +# Layouts +from analyzer import analyzer_layout +from database import database_editor_layout + +# External +from imgui_bundle import imgui, immapp, hello_imgui, ImVec2 + +# Built In +import shelve +from typing import List + +def menu_bar(runner_params: hello_imgui.RunnerParams) -> None: + hello_imgui.show_app_menu(runner_params) + hello_imgui.show_view_menu(runner_params) + if imgui.begin_menu("File"): + clicked, _ = imgui.menu_item("Open", "", False) + if clicked: + pass + imgui.end_menu() + +def status_bar(app_state: AppState) -> None: + imgui.text("Student Analyzer by @DerGrumpf") + +def main() -> None: + app_state = AppState() - # self.io = imgui.get_io() + # Load Database + with shelve.open("state") as state: + v = state.get("DB") + if v: + db.init(v) + db.connect() + db.create_tables([Class, Student, Lecture, Submission]) + app_state.update() - # Global GUI Setting - '''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.view = View() + # Set Asset Folder + #hello_imgui.set_assets_folder() - def header(self): - imgui.set_next_window_size(io.display_size.x, io.display_size.y*0.03) - imgui.set_next_window_position(0, io.display_size.y*0.02) + # Set Theme - with imgui.begin("HEADER", False, imgui.WINDOW_NO_MOVE | imgui.WINDOW_NO_RESIZE | imgui.WINDOW_NO_COLLAPSE | imgui.WINDOW_NO_TITLE_BAR): - imgui.set_window_font_scale(1.3) - text = "Student Analyzer" - ww = imgui.get_window_size().x - tw = imgui.calc_text_size(text).x - imgui.set_cursor_pos_x((ww - tw) * 0.5) - imgui.text("Student Analyzer") - - def __call__(self): - self.view() + # Set Window Params + runner_params = hello_imgui.RunnerParams() + runner_params.app_window_params.window_title = "Analyzer" + runner_params.imgui_window_params.menu_app_title = "Analyzer" + 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 + # Load Fonts + #runner_params.callbacks.load_additional_fonts = lambda: f() - -if __name__ == "__main__": - immapp.run( - gui_function=GUI(), - window_title="Student Analyzer", - window_size_auto=True, - with_implot=True, - with_markdown=True + # Status Bar & Main Menu + 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 + runner_params.imgui_window_params.show_status_bar = True + # 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: menu_bar(runner_params) + # Optional: add items to Hello ImGui default App menu + #runner_params.callbacks.show_app_menu_items = show_app_menu_items + runner_params.callbacks.show_status = lambda: status_bar(app_state) + + # Application layout + runner_params.imgui_window_params.default_imgui_window_type = ( + hello_imgui.DefaultImGuiWindowType.provide_full_screen_dock_space ) + runner_params.imgui_window_params.enable_viewports = True + + runner_params.docking_params = analyzer_layout(app_state) + runner_params.alternative_docking_layouts = [ + database_editor_layout(app_state) + ] + + # Save App Settings + runner_params.ini_folder_type = hello_imgui.IniFolderType.app_user_config_folder + runner_params.ini_filename = "Analyzer/Analyzer.ini" + + # Uncomment if layout will stay the same at start + runner_params.docking_params.layout_condition = hello_imgui.DockingLayoutCondition.application_start + + # Run it + add_ons_params = immapp.AddOnsParams() + add_ons_params.with_markdown = True + add_ons_params.with_implot = True + add_ons_params.with_implot3d = True + immapp.run(runner_params, add_ons_params) + +if __name__ == "__main__": + main() diff --git a/lecture_editor.py b/lecture_editor.py deleted file mode 100644 index 235f082..0000000 --- a/lecture_editor.py +++ /dev/null @@ -1,44 +0,0 @@ -from imgui_bundle import imgui -from datatypes import * - -from model import * - -class LectureEditor: - def __init__(self): - super().__init__() - - self.select = 0 - self.add_lecture_text = str() - self.add_lecture_points = 0 - - def __call__(self, clas: Class): - id = clas.id if clas else None - lectures = Lecture.select().where(Lecture.class_id == id) if id else None - - with imgui.begin("Lecture Editor", False, imgui.WINDOW_NO_MOVE | imgui.WINDOW_NO_RESIZE | imgui.WINDOW_NO_COLLAPSE): - imgui.text("Add Lecture") - _, self.add_lecture_text = imgui.input_text("Title", self.add_lecture_text) - if self.add_lecture_points < 0: - self.add_lecture_points = 0 - _, self.add_lecture_points = imgui.input_int("Points", self.add_lecture_points) - - if imgui.button("Add"): - Lecture.create( - title=self.add_lecture_text, - points=self.add_lecture_points, - class_id=id - ) - - imgui.separator() - - if not lectures: - imgui.text("No Lectures could be queried") - return - - for n, lecture in enumerate(lectures, start=1): - display = f"{n}. {lecture.title}" - opened, _ = imgui.selectable(display, self.select == n-1) - if opened: - self.select = n-1 - - return lectures[self.select] diff --git a/main_menu.py b/main_menu.py deleted file mode 100644 index 94c8a96..0000000 --- a/main_menu.py +++ /dev/null @@ -1,37 +0,0 @@ -from imgui_bundle import imgui, imgui_ctx - -from datatypes import * - -class MainMenu: - def __init__(self): - super().__init__() - - self.new = False - self.new_text = str() - - def __call__(self): - if self.new: - self.create_new_file() - - with imgui_ctx.begin_main_menu_bar() as main_menu_bar: - if main_menu_bar: - 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_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_simple(l.name.title()) - if clicked: - return l - - def create_new_file(self): - pass diff --git a/model.py b/model.py index 56aa96d..6036c92 100644 --- a/model.py +++ b/model.py @@ -123,10 +123,6 @@ def dump_to_json(fp: Path, indent=None) -> None: with open(fp, "w") as file: json.dump(d, file, indent=indent) -db.init('test.db') -db.connect() -db.create_tables([Class, Student, Lecture, Submission]) - def main(): import random # Generate Test Data @@ -166,4 +162,6 @@ def main(): if __name__ == "__main__": # main() + db.init('wise_24_25.db') + db.connect() dump_to_json(Path().cwd()/"TEST.json") diff --git a/state b/state new file mode 100644 index 0000000000000000000000000000000000000000..7135d886abbae2699c650eb6a672550df1e53fc6 GIT binary patch literal 16384 zcmeIyu?>Pi6vpw>*jQoW0#4u=)Yuxr1{Rb?BM}l20uJB+mfARgtqZt=3z_Jhcf}R9 zCSv|1@D2)i{POO*pQEnm2=P8u(Y0;sMc1jm2=Oq5D*^~0fB*srAbbHE(156A&>fE*wP$N_SI93Th&EC(89SBslx7U94?_(IX&daC2o zt9fozaKORcA9b0BtJjFaKy_~Oqp6nqwl4kCrF0fp-l2DmPL|oWKrhpa*<`}b}iMSNa9j1sw&=9GD>Ih+#k*TaeuWu d_TB$xjQ|1&Ab lecture.points*0.3 else COLOR_TEXT_FAILED - imgui.text_colored(display, *COLOR) diff --git a/student_info.py b/student_info.py deleted file mode 100644 index 325d695..0000000 --- a/student_info.py +++ /dev/null @@ -1,77 +0,0 @@ -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 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) - - 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() - 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 deleted file mode 100644 index 7ec48e3..0000000 --- a/student_list.py +++ /dev/null @@ -1,27 +0,0 @@ -from model import * -from imgui_bundle import imgui - -class StudentList: - def __init__(self): - super().__init__() - - self.select: int = 0 - - def __call__(self, clas: Class): - id = clas.id if clas else None - students = Student.select().where(Student.class_id == id) if id else None - - with imgui.begin("Student Table", False, imgui.WINDOW_NO_MOVE | imgui.WINDOW_NO_RESIZE | imgui.WINDOW_NO_COLLAPSE): - if not students: - imgui.text("No Dataset could be queried") - return - - for n, student in enumerate(students, start=1): - display = f"{n}. {student.prename} {student.surname}" - opened, _ = imgui.selectable(display, self.select == n-1) - if opened: - self.select = n-1 - - return students[self.select] - - diff --git a/student_ranking.py b/student_ranking.py deleted file mode 100644 index 6add0d0..0000000 --- a/student_ranking.py +++ /dev/null @@ -1,37 +0,0 @@ -import numpy as np -from imgui_bundle import imgui - -from model import * - -class StudentRanking: - def __init__(self): - super().__init__() - - def __call__(self): - students = Student.select().where(Student.class_id == 1) - lectures = Lecture.select().where(Lecture.class_id == 1) - - overall_points = sum([l.points for l in lectures]) - - ranking = list() - avg = list() - for s in students: - rank = sum([sub.points for sub in Submission.select().where(Submission.student_id == s.id)])/overall_points - ranking.append((f"{s.prename} {s.surname}", rank)) - avg.append(rank) - ranking = sorted(ranking, key=lambda x: x[1], reverse=True) - avg = sum(avg)/len(avg) - - flag = True - - with imgui.begin("Student Ranking", False, imgui.WINDOW_NO_MOVE | imgui.WINDOW_NO_RESIZE | imgui.WINDOW_NO_COLLAPSE): - for n, rank in enumerate(ranking, start=1): - if rank[1] < avg and flag: - imgui.separator() - flag = False - - imgui.text(f"{n}. {rank[0]} {rank[1]:.1%}") - - imgui.separator() - imgui.text(f"Average: {avg:.1%}") - diff --git a/submission_editor.py b/submission_editor.py deleted file mode 100644 index a231fe5..0000000 --- a/submission_editor.py +++ /dev/null @@ -1,66 +0,0 @@ -from imgui_bundle import imgui -from model import * - -class SubmissionEditor: - def __init__(self): - super().__init__() - self.current_lecture = 0 - self.current_student = 0 - self.points = 0 - - def __call__(self, clas: Class): - id = clas.id if clas else None - lectures = Lecture.select().where(Lecture.class_id == id) if id else None - students = Student.select().where(Student.class_id == id) if id else None - - with imgui.begin("Submission Editor", False, imgui.WINDOW_NO_MOVE | imgui.WINDOW_NO_RESIZE | imgui.WINDOW_NO_COLLAPSE): - imgui.text("Add Submission") - - if not lectures: - imgui.text("No Lectures queried") - return - - if not students: - imgui.text("No Students queried") - return - - _, self.current_lecture = imgui.combo("Lecture", self.current_lecture, [f"{l.title} ({l.points})" for l in lectures]) - _, self.current_student = imgui.combo("Student", self.current_student, [f"{s.prename} {s.surname}" for s in students]) - - if self.points < 0: - self.points = 0 - - max = lectures[self.current_lecture].points - if self.points > max: - self.points = max - - _, self.points = imgui.input_float("Points", self.points, format='%.1f', step=0.5, step_fast=1.0) - - if imgui.button("Add"): - if not Submission.select().where( - Submission.student_id == students[self.current_student].id and - Submission.lecture_id == lectures[self.current_lecture].id - ): - Submission.create( - student_id=students[self.current_student].id, - lecture_id=lectures[self.current_lecture].id, - points=self.points - ) - - imgui.same_line() - - if imgui.button("Update"): - submission = Submission.select().where( - Submission.student_id == students[self.current_student].id and - Submission.lecture_id == lectures[self.current_lecture].id - ).get() - submission.points = self.points - submission.save() - - imgui.same_line() - - if imgui.button("Delete"): - Submission.delete().where( - Submission.student_id == students[self.current_student].id and - Submission.lecture_id == lectures[self.current_lecture].id - ).execute() diff --git a/view.py b/view.py deleted file mode 100644 index 9e3e55a..0000000 --- a/view.py +++ /dev/null @@ -1,65 +0,0 @@ -from imgui_bundle import imgui, ImVec2 - -from datatypes import * - -from main_menu import MainMenu - -from database_editor import DatabaseEditor - -from student_info import StudentInfo - -def set_layout(size: tuple, pos: tuple) -> None: - io = imgui.get_io() - 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_info = StudentInfo() - - def set_layout(self): - pass - - def __call__(self): - self.student_info() - -class EditorLayout: - def __init__(self): - super().__init__() - self.database_editor = DatabaseEditor() - - def set_layout(self): - pass - - def __call__(self): - self.database_editor() - -class View: - def __init__(self): - super().__init__() - 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: - self.current = option - - if self.current == LayoutOptions.EDITOR: - self.editor() - - if self.current == LayoutOptions.GRAPHER: - self.grapher()