A downloadable project

Source Code:

#!/usr/bin/env python3

"""

Calculuaⁿ (Alpha 0.6)

Features include:

  - A full-screen analysis panel with a horizontal scrollable category bar

  - Inverse trigonometric helpers for degrees and statistics functions

  - A settings popup for angle mode, UI theme, and decimal places

  - A plugins screen and multiple splash texts

"""


import random

from kivy.app import App

from kivy.uix.boxlayout import BoxLayout

from kivy.uix.floatlayout import FloatLayout

from kivy.uix.scrollview import ScrollView

from kivy.uix.label import Label

from kivy.uix.textinput import TextInput

from kivy.uix.button import Button

from kivy.uix.gridlayout import GridLayout

from kivy.uix.popup import Popup

from kivy.uix.widget import Widget

from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition

from kivy.core.window import Window

from kivy.graphics import Color, Rectangle

from kivy.clock import Clock

from kivy.animation import Animation

from kivy.core.clipboard import Clipboard


from sympy import (sympify, sqrt, Eq, solve, sin, cos, tan, asin, acos, atan,

                   pi, SympifyError, Basic, hyper, Si, Ci, factorial)

from sympy.combinatorics import SymmetricGroup, AlternatingGroup, CyclicGroup, DihedralGroup

from sympy.combinatorics.free_groups import free_group

from sympy.combinatorics.perm_groups import PermutationGroup


# (Removed get_step_by_step since step-by-step solutions are no longer needed)


class FallingEquations(Widget):

    def __init__(self, **kwargs):

        super().__init__(**kwargs)

        self.equations = [

            "E = mc²", "a² + b² = c²", "∫f(x)dx", "π ≈ 3.14",

            "sin(x) = x", "x² + y² = z²", "d/dx (x²) = 2x",

            "1 + x = x + 1", "3 - 2 = 1", "a + bi", "0", "1", "2",

            "3", "4", "5", "6", "7", "8", "9", "10"

        ]

        Clock.schedule_interval(self.spawn_equation, 1.0)

    def spawn_equation(self, dt):

        eq_text = random.choice(self.equations)

        eq_label = Label(text=eq_text, font_size='20sp', color=(1, 1, 1, 0.3))

        eq_label.texture_update()

        label_width = eq_label.texture_size[0]

        start_x = random.uniform(0, max(Window.width - label_width, 0))

        eq_label.pos = (start_x, self.height)

        self.add_widget(eq_label)

        duration = random.uniform(4, 8)

        anim = Animation(y=-eq_label.texture_size[1], duration=duration)

        anim.bind(on_complete=lambda a, widget: self.remove_widget(widget))

        anim.start(eq_label)


class TitleScreenLayout(FloatLayout):

    def __init__(self, **kwargs):

        super().__init__(**kwargs)

        with self.canvas.before:

            Color(0, 0, 0, 1)

            self.rect = Rectangle(pos=self.pos, size=Window.size)

        self.bind(size=self.update_rect, pos=self.update_rect)

        self.add_widget(FallingEquations())

        top_box = BoxLayout(orientation='vertical', size_hint=(0.8, 0.25),

                             pos_hint={'center_x': 0.5, 'top': 0.95}, spacing=10)

        self.add_widget(top_box)

        logo = Label(text="[b]Calculuaⁿ[/b]", markup=True, font_size='64sp',

                     color=(1, 1, 1, 1), size_hint=(1, None))

        logo.texture_update(); logo.height = logo.texture_size[1]

        top_box.add_widget(logo)

        splash = Label(text=random.choice([

            "Crunch numbers and decode algebra!",

            "Plug in, solve, and celebrate!",

            "Get ready to compute your world!",

            "Malcolm, this might be for you.",

            "Symbolab who?",

            "Who reads these?",

            "Goldmine of mathematics...",

            "Beware of abstract algebra..."

        ]), font_size='20sp', color=(1, 1, 1, 1), size_hint=(1, None))

        splash.texture_update(); splash.height = splash.texture_size[1]

        top_box.add_widget(splash)

        btn_layout = BoxLayout(orientation='vertical', size_hint=(0.6, 0.3),

                                pos_hint={'center_x': 0.5, 'y': 0.1}, spacing=20)

        start_btn = Button(text="Start Calculating", font_size='24sp',

                           background_color=(0.1, 0.1, 0.3, 1), color=(1, 1, 1, 1))

        start_btn.bind(on_press=lambda inst: setattr(App.get_running_app().root, 'current', 'calc'))

        plugins_btn = Button(text="Plugins", font_size='24sp',

                             background_color=(0.1, 0.1, 0.3, 1), color=(1, 1, 1, 1))

        plugins_btn.bind(on_press=lambda inst: setattr(App.get_running_app().root, 'current', 'plugins'))

        exit_btn = Button(text="Exit", font_size='24sp',

                          background_color=(0.5, 0, 0, 1), color=(1, 1, 1, 1))

        exit_btn.bind(on_press=lambda _: App.get_running_app().stop())

        btn_layout.add_widget(start_btn)

        btn_layout.add_widget(plugins_btn)

        btn_layout.add_widget(exit_btn)

        self.add_widget(btn_layout)

    def update_rect(self, *args):

        self.rect.pos = self.pos; self.rect.size = self.size


class PluginsScreen(Screen):

    def __init__(self, **kwargs):

        super().__init__(**kwargs)

        self.name = "plugins"

        layout = BoxLayout(orientation='vertical', padding=20, spacing=20)

        layout.add_widget(Label(text="[b]Plugins[/b]\n\nPlugins coming soon!", markup=True, font_size='32sp'))

        back_btn = Button(text="Back", size_hint=(1, 0.2), font_size='24sp',

                          background_color=(0.1, 0.1, 0.3, 1), color=(1, 1, 1, 1))

        back_btn.bind(on_press=lambda inst: setattr(App.get_running_app().root, 'current', 'title'))

        layout.add_widget(back_btn)

        self.add_widget(layout)


class TitleScreen(Screen):

    def __init__(self, **kwargs):

        super().__init__(**kwargs)

        self.name = "title"

        self.add_widget(TitleScreenLayout())


class CalculatorScreen(Screen):

    def __init__(self, **kwargs):

        super().__init__(**kwargs)

        self.name = "calc"

        self.add_widget(Calculuan())


def mean_func(*vals):

    from math import fsum

    arr = [float(v.evalf()) for v in vals]

    return fsum(arr) / len(arr)

def median_func(*vals):

    arr = sorted([float(v.evalf()) for v in vals])

    n = len(arr)

    return arr[n // 2] if n % 2 == 1 else (arr[n // 2 - 1] + arr[n // 2]) / 2

def mode_func(*vals):

    from collections import Counter

    arr = [float(v.evalf()) for v in vals]

    c = Counter(arr); max_count = max(c.values())

    for k in c:

        if c[k] == max_count:

            return k

def stdev_func(*vals):

    import math

    arr = [float(v.evalf()) for v in vals]; n = len(arr)

    m = sum(arr) / n; var = sum((x - m) ** 2 for x in arr) / n

    return math.sqrt(var)

def variance_func(*vals):

    arr = [float(v.evalf()) for v in vals]; n = len(arr)

    m = sum(arr) / n; return sum((x - m) ** 2 for x in arr) / n


def sin_deg(x): return sin(x * pi / 180)

def cos_deg(x): return cos(x * pi / 180)

def tan_deg(x): return tan(x * pi / 180)

def asin_deg(x): return asin(x) * 180 / pi

def acos_deg(x): return acos(x) * 180 / pi

def atan_deg(x): return atan(x) * 180 / pi


def approximate_result(res, digits=5):

    if isinstance(res, dict):

        return {str(k): approximate_result(v, digits) for k, v in res.items()}

    elif isinstance(res, (list, tuple)):

        return [approximate_result(x, digits) for x in res]

    elif isinstance(res, Basic):

        try:

            return f"{float(res.evalf(digits + 2)):.{digits}f}"

        except:

            return str(res)

    elif isinstance(res, float):

        return f"{res:.{digits}f}"

    return res


class Calculuan(BoxLayout):

    def __init__(self, **kwargs):

        super().__init__(**kwargs)

        self.orientation = 'vertical'; self.padding = 20; self.spacing = 15

        self.angle_mode = "radians"; self.current_theme = "Blue"; self.decimal_places = 5

        self.ui_themes = {

            "Red": (0.5, 0, 0, 1), "Green": (0, 0.5, 0, 1),

            "Blue": (0, 0, 0.5, 1), "Purple": (0.4, 0, 0.4, 1),

            "Pink": (1, 0.4, 0.6, 1), "Orange": (1, 0.5, 0, 1),

            "Yellow": (1, 1, 0, 1), "Gray": (0.5, 0.5, 0.5, 1),

            "Black": (0, 0, 0, 1)

        }

        Window.clearcolor = self.ui_themes[self.current_theme]

        with self.canvas.before:

            Color(*self.ui_themes[self.current_theme])

            self.rect = Rectangle(pos=self.pos, size=self.size)

        self.bind(pos=self.update_rect, size=self.update_rect)

        top_bar = BoxLayout(orientation='horizontal', size_hint=(1, 0.2))

        self.title_label = Label(text="[b]Welcome to Calculuaⁿ![/b]\n[i]Enter an expression.[/i]",

                                  halign='left', markup=True, font_size='20sp', color=(1, 1, 1, 1))

        top_bar.add_widget(self.title_label)

        settings_btn = Button(text="Settings", size_hint=(0.2, 1), font_size='24sp',

                              background_color=(0.1, 0.1, 0.3, 1), color=(1, 1, 1, 1))

        settings_btn.bind(on_press=self.open_settings)

        top_bar.add_widget(settings_btn)

        self.add_widget(top_bar)

        self.input_expr = TextInput(multiline=False, hint_text="Enter expression here",

                                    size_hint=(1, 0.15), background_color=(0.2, 0.2, 0.2, 1),

                                    foreground_color=(1, 1, 1, 1), padding=[10, 10, 10, 10], font_size='18sp')

        self.add_widget(self.input_expr)

        category_scroll = ScrollView(size_hint=(1, 0.15), do_scroll_x=True, do_scroll_y=False)

        self.category_layout = BoxLayout(orientation='horizontal', spacing=10, size_hint=(None, 1))

        self.category_layout.bind(minimum_width=self.category_layout.setter('width'))

        category_scroll.add_widget(self.category_layout)

        self.add_widget(category_scroll)

        self.categories = [

            "Arithmetic", "Functions", "Trigonometry", "Calculus",

            "Special Functions", "Complex", "Number Theory", "Linear Algebra",

            "Statistics", "Abstract Algebra", "Set Theory"

        ]

        self.keyboard_categories = {

            "Arithmetic": [["7", "8", "9", "/", "("],

                           ["4", "5", "6", "*", ")"],

                           ["1", "2", "3", "-", "Del"],

                           ["0", ".", "+", "Clear", ","]],

            "Functions": [["sqrt(", "**", "x", "log(", "exp("],

                          ["abs(", "factor(", "=", "pi", "E"]],

            "Trigonometry": [["sin(", "cos(", "tan(", "asin(", "acos("],

                             ["atan(", "", "", "", ""]],

            "Calculus": [["integrate(", "diff(", "limit(", "summation(", "product("]],

            "Special Functions": [["gamma(", "zeta(", "erf(", "LambertW(", "factorial("],

                                  ["Si(", "Ci(", "hyper(", "", ""]],

            "Complex": [["I", "conjugate(", "re(", "im(", ""],

                        ["", "", "", "", ""]],

            "Number Theory": [["gcd(", "lcm(", "isprime(", "nextprime(", ""],

                              ["factorint(", "divisors(", "", "", ""]],

            "Linear Algebra": [["Matrix(", "det(", "DefineMatrix", "", ""]],

            "Statistics": [["mean(", "median(", "mode(", "stdev(", "variance("],

                           ["", "", "", "", ""]],

            "Abstract Algebra": [["SymmetricGroup(", "AlternatingGroup(", "CyclicGroup(", "DihedralGroup(", "FreeGroup("]],

            "Set Theory": [["Union(", "Intersection(", "SetDiff(", "SymDiff(", "{}"],

                           ["EmptySet", "Subset(", "Superset(", "Complement(", ""]]

        }

        for cat in self.categories:

            btn = Button(text=cat, size_hint=(None, 1), width=180, font_size=16,

                         halign='center', valign='middle', background_color=(0.1, 0.1, 0.3, 1),

                         color=(1, 1, 1, 1))

            btn.bind(on_press=lambda inst, c=cat: self.switch_keyboard(c))

            self.category_layout.add_widget(btn)

        self.keyboard_container = BoxLayout(size_hint=(1, 0.35))

        self.add_widget(self.keyboard_container)

        self.current_category = "Arithmetic"

        self.current_keyboard = self.build_keyboard_grid(self.current_category)

        self.keyboard_container.add_widget(self.current_keyboard)

        self.compute_btn = Button(text="Compute", size_hint=(1, 0.2),

                                  background_color=(0.1, 0.1, 0.3, 1), color=(1, 1, 1, 1),

                                  font_size='18sp')

        self.compute_btn.bind(on_press=self.evaluate)

        self.add_widget(self.compute_btn)

        version_box = FloatLayout(size_hint=(1, 0.05))

        self.add_widget(version_box)

        self.version_label = Label(text="Alpha 0.6", size_hint=(None, None),

                                   pos_hint={"right": 1, "bottom": 1},

                                   color=(1, 1, 1, 0.7), font_size='14sp')

        version_box.add_widget(self.version_label)

    def update_rect(self, *args):

        self.rect.pos = self.pos; self.rect.size = self.size

    def build_keyboard_grid(self, category):

        grid = GridLayout(cols=5, spacing=5)

        for row in self.keyboard_categories.get(category, []):

            for key in row:

                if key == "":

                    grid.add_widget(Label(text=""))

                else:

                    btn = Button(text=key, background_color=(0.1, 0.1, 0.3, 1),

                                 color=(1, 1, 1, 1), font_size='16sp')

                    btn.bind(on_press=self.on_key_press)

                    grid.add_widget(btn)

        return grid

    def switch_keyboard(self, category):

        self.current_category = category

        self.keyboard_container.clear_widgets()

        self.keyboard_container.add_widget(self.build_keyboard_grid(category))

    def on_key_press(self, instance):

        key = instance.text

        if key == "DefineMatrix":

            self.open_matrix_popup()

            return

        if key == "Del":

            self.input_expr.text = self.input_expr.text[:-1]

        elif key == "Clear":

            self.input_expr.text = ""

        else:

            self.input_expr.text += key

    def open_matrix_popup(self):

        layout = BoxLayout(orientation='vertical', spacing=10, padding=10)

        row_box = BoxLayout(orientation='horizontal', spacing=10)

        row_box.add_widget(Label(text="Rows:", size_hint=(0.4, 1)))

        row_input = TextInput(text="2", multiline=False, size_hint=(0.6, 1), input_filter='int')

        row_box.add_widget(row_input)

        col_box = BoxLayout(orientation='horizontal', spacing=10)

        col_box.add_widget(Label(text="Columns:", size_hint=(0.4, 1)))

        col_input = TextInput(text="2", multiline=False, size_hint=(0.6, 1), input_filter='int')

        col_box.add_widget(col_input)

        layout.add_widget(row_box); layout.add_widget(col_box)

        btn_box = BoxLayout(orientation='horizontal', spacing=10, size_hint=(1, 0.3))

        ok_btn = Button(text="OK", background_color=(0.1, 0.5, 0.1, 1), color=(1, 1, 1, 1))

        cancel_btn = Button(text="Cancel", background_color=(0.5, 0.1, 0.1, 1), color=(1, 1, 1, 1))

        btn_box.add_widget(ok_btn); btn_box.add_widget(cancel_btn); layout.add_widget(btn_box)

        matrix_popup = Popup(title="Define Matrix Size", content=layout, size_hint=(0.8, 0.4))

        def on_ok(_):

            try:

                rows = int(row_input.text); cols = int(col_input.text)

                if rows < 1 or cols < 1:

                    raise ValueError("Rows and columns must be >= 1.")

                if rows > 7 or cols > 7:

                    raise ValueError("Max 7 rows or columns allowed.")

                matrix_str = "Matrix([" + ",".join([f"[{','.join(['0']*cols)}]" for _ in range(rows)]) + "])"

                self.input_expr.text += matrix_str; matrix_popup.dismiss()

            except Exception as e:

                self.input_expr.text += f" # Error: {e}"; matrix_popup.dismiss()

        ok_btn.bind(on_press=on_ok); cancel_btn.bind(on_press=lambda _: matrix_popup.dismiss())

        matrix_popup.open()

    def open_settings(self, _):

        content = BoxLayout(orientation='vertical', spacing=10, padding=10)

        mode_bar = BoxLayout(orientation='horizontal', spacing=10)

        degrees_btn = Button(text="Degrees", font_size='16sp', background_color=(0.2,0.2,0.2,1), color=(1,1,1,1))

        radians_btn = Button(text="Radians", font_size='16sp', background_color=(0.2,0.2,0.2,1), color=(1,1,1,1))

        def set_degrees(_): self.angle_mode = "degrees"; degrees_btn.background_color = (0.1,0.6,0.1,1); radians_btn.background_color = (0.2,0.2,0.2,1)

        def set_radians(_): self.angle_mode = "radians"; radians_btn.background_color = (0.1,0.6,0.1,1); degrees_btn.background_color = (0.2,0.2,0.2,1)

        if self.angle_mode == "degrees":

            degrees_btn.background_color = (0.1,0.6,0.1,1)

        else:

            radians_btn.background_color = (0.1,0.6,0.1,1)

        degrees_btn.bind(on_press=set_degrees); radians_btn.bind(on_press=set_radians)

        mode_bar.add_widget(degrees_btn); mode_bar.add_widget(radians_btn)

        theme_bar = BoxLayout(orientation='horizontal', spacing=10)

        for theme in self.ui_themes.keys():

            btn = Button(text=theme, font_size='16sp', background_color=(0.2,0.2,0.2,1), color=(1,1,1,1))

            def on_theme_press(btn_inst, theme=theme):

                self.current_theme = theme; self.update_theme()

                for child in theme_bar.children:

                    child.background_color = (0.2,0.2,0.2,1)

                btn_inst.background_color = (0.1,0.6,0.1,1)

            if theme == self.current_theme:

                btn.background_color = (0.1,0.6,0.1,1)

            btn.bind(on_press=on_theme_press); theme_bar.add_widget(btn)

        decimal_box = BoxLayout(orientation='horizontal', spacing=10)

        decimal_box.add_widget(Label(text="Decimal Places:", font_size='18sp', color=(1,1,1,1)))

        decimal_input = TextInput(text=str(self.decimal_places), multiline=False,

                                   size_hint=(0.3,1), input_filter='int',

                                   background_color=(0.2,0.2,0.2,1), foreground_color=(1,1,1,1))

        apply_btn = Button(text="Apply", font_size='16sp',

                           background_color=(0.1,0.3,0.1,1), color=(1,1,1,1),

                           size_hint=(0.3,1))

        def apply_settings(_):

            try:

                val = int(decimal_input.text)

                if val < 1 or val > 15:

                    raise ValueError("Decimal places must be 1..15")

                self.decimal_places = val

            except Exception as e:

                decimal_input.text = str(self.decimal_places)

        apply_btn.bind(on_press=apply_settings)

        decimal_box.add_widget(decimal_input); decimal_box.add_widget(apply_btn)

        content.add_widget(mode_bar); content.add_widget(theme_bar); content.add_widget(decimal_box)

        Popup(title="Settings", content=content, size_hint=(0.9,0.7)).open()

    def update_theme(self):

        new_color = self.ui_themes[self.current_theme]

        Window.clearcolor = new_color; self.canvas.before.clear()

        with self.canvas.before:

            Color(*new_color); self.rect = Rectangle(pos=self.pos, size=self.size)

    def evaluate_expression(self, expr):

        from sympy import Eq, solve

        try:

            trig_locals = {"sin": sin_deg, "cos": cos_deg, "tan": tan_deg} if self.angle_mode=="degrees" else {"sin": sin, "cos": cos, "tan": tan}

            stats_locals = {"mean": mean_func, "median": median_func, "mode": mode_func, "stdev": stdev_func, "variance": variance_func}

            base_locals = {"sqrt": sqrt, "pi": pi, "Si": Si, "Ci": Ci, "hyper": hyper, "factorial": factorial,

                           "SymmetricGroup": SymmetricGroup, "AlternatingGroup": AlternatingGroup,

                           "CyclicGroup": CyclicGroup, "DihedralGroup": DihedralGroup, "FreeGroup": free_group}

            locals_dict = {}; locals_dict.update(base_locals); locals_dict.update(trig_locals); locals_dict.update(stats_locals)

            if '=' in expr:

                parts = expr.split('=')

                if len(parts) != 2:

                    return "Error: Equation must have exactly one '=' sign."

                left_expr = sympify(parts[0].strip(), locals=locals_dict)

                right_expr = sympify(parts[1].strip(), locals=locals_dict)

                symbols = list(left_expr.free_symbols.union(right_expr.free_symbols))

                raw_result = solve(Eq(left_expr, right_expr), symbols) if symbols else (left_expr == right_expr)

            else:

                raw_result = sympify(expr, locals=locals_dict)

            disp_result = approximate_result(raw_result, self.decimal_places) if not isinstance(raw_result, PermutationGroup) else raw_result

            return (raw_result, disp_result)

        except ZeroDivisionError as e:

            return f"Division by zero error: {e}"

        except SympifyError as e:

            return f"Error parsing expression: {e}"

        except Exception as e:

            return f"An error occurred: {e}"

    def open_analysis_panel(self, expr, eval_result):

        from sympy import Eq, solve

        if isinstance(eval_result, tuple):

            raw_result, disp_result = eval_result

        else:

            raw_result = eval_result; disp_result = approximate_result(eval_result, self.decimal_places)

        locals_dict = {"sin": sin_deg, "cos": cos_deg, "tan": tan_deg} if self.angle_mode=="degrees" else {"sin": sin, "cos": cos, "tan": tan}

        locals_dict.update({"sqrt": sqrt, "pi": pi, "Si": Si, "Ci": Ci, "hyper": hyper, "factorial": factorial,

                            "SymmetricGroup": SymmetricGroup, "AlternatingGroup": AlternatingGroup,

                            "CyclicGroup": CyclicGroup, "DihedralGroup": DihedralGroup, "FreeGroup": free_group})

        try:

            base_expr = sympify(expr, locals=locals_dict)

        except Exception:

            base_expr = expr

        analysis_items = [("Original Expression", str(expr)), ("Evaluated Result", str(disp_result))]

        if hasattr(base_expr, "free_symbols") and base_expr.free_symbols:

            for method, label in [(lambda e: e.simplify(), "Simplified"),

                                  (lambda e: e.expand(), "Expanded"),

                                  (lambda e: e.factor(), "Factorized"),

                                  (lambda e: e.diff(), "Derivative"),

                                  (lambda e: e.integrate(), "Integral")]:

                try:

                    analysis_items.append((label, str(method(base_expr))))

                except Exception as e:

                    analysis_items.append((label, f"Error: {e}"))

        if '=' in expr:

            try:

                parts = expr.split('=')

                left_expr = sympify(parts[0].strip(), locals=locals_dict)

                right_expr = sympify(parts[1].strip(), locals=locals_dict)

                sol = solve(Eq(left_expr, right_expr))

                analysis_items.append(("Roots", str(approximate_result(sol, self.decimal_places))))

            except Exception as e:

                analysis_items.append(("Roots", f"Error: {e}"))

        group_keywords = ["SymmetricGroup(", "AlternatingGroup(", "CyclicGroup(", "DihedralGroup(", "FreeGroup("]

        if any(keyword in expr for keyword in group_keywords):

            if isinstance(raw_result, PermutationGroup):

                for label, method in [("Group Order", lambda g: g.order()),

                                      ("Group Degree", lambda g: g.degree),

                                      ("Group Generators", lambda g: ", ".join(str(gen) for gen in g.generators))]:

                    try:

                        analysis_items.append((label, str(method(raw_result))))

                    except Exception as e:

                        analysis_items.append((label, f"Error: {e}"))

            else:

                analysis_items.append(("Abstract Algebra Info", "Result in concrete permutation representation."))

        analysis_layout = BoxLayout(orientation='vertical', spacing=10, padding=10)

        panel_top = BoxLayout(orientation='horizontal', size_hint=(1, 0.1))

        panel_title = Label(text="[b]Analysis Panel[/b]", markup=True, font_size='24sp',

                            color=(1,1,1,1), size_hint=(0.6,1))

        copy_btn = Button(text="Copy", size_hint=(0.2, 1), font_size='24sp',

                          background_color=(0.2,0.2,0.2,1), color=(1,1,1,1))

        close_btn = Button(text="X", size_hint=(0.2, 1), font_size='24sp',

                           background_color=(1,0,0,1), color=(1,1,1,1))

        panel_top.add_widget(panel_title); panel_top.add_widget(copy_btn); panel_top.add_widget(close_btn)

        analysis_layout.add_widget(panel_top)

        def copy_analysis(_):

            Clipboard.copy("\n".join(f"{title}: {content}" for title, content in analysis_items))

        copy_btn.bind(on_press=copy_analysis)

        scroll = ScrollView(size_hint=(1, 0.9))

        details_layout = BoxLayout(orientation='vertical', spacing=10, size_hint_y=None, padding=10)

        details_layout.bind(minimum_height=details_layout.setter('height'))

        for title, content in analysis_items:

            if content and content != "None":

                lbl = Label(text=f"[b]{title}:[/b] {content}", markup=True,

                            font_size='24sp', color=(1,1,1,1), halign='left', valign='top', size_hint_y=None)

                lbl.bind(texture_size=lbl.setter('size')); lbl.text_size = (Window.width * 0.9, None)

                details_layout.add_widget(lbl)

        scroll.add_widget(details_layout); analysis_layout.add_widget(scroll)

        panel_popup = Popup(title="", content=analysis_layout, size_hint=(1,1))

        panel_popup.background = ""; panel_popup.background_color = (0,0,0.8,1)

        close_btn.bind(on_press=lambda _: panel_popup.dismiss()); panel_popup.open()

    def evaluate(self, _):

        user_expr = self.input_expr.text

        if user_expr.lower() in ['quit', 'exit']:

            App.get_running_app().stop()

        result = self.evaluate_expression(user_expr)

        self.open_analysis_panel(user_expr, result)


class CalculuanApp(App):

    def build(self):

        Window.title = "Calculuaⁿ"

        sm = ScreenManager(transition=FadeTransition())

        sm.add_widget(TitleScreen())

        sm.add_widget(CalculatorScreen())

        sm.add_widget(PluginsScreen())

        sm.current = "title"

        return sm


if __name__ == '__main__':

    CalculuanApp().run()

Leave a comment

Log in with itch.io to leave a comment.