Skip to content

Notepad Custom Module Example

This demonstrates how to create a custom module using Debby's element library.

To use this example:

  1. Copy this file to your project's Scripts folder
  2. After Debby initialization, register the module: Debby.RegisterCustomModule<NotepadModule>();

This module provides a simple notepad for taking notes during testing/debugging. Notes are persisted between sessions using PlayerPrefs.

csharp
using Debology.Debby;
using Debology.Debby.Elements;
using Debology.Debby.Modules;
using Debology.Debby.Modules.Adjust;
using UnityEngine;
using UnityEngine.UIElements;

/// <summary>
/// A simple notepad module for taking notes during testing and debugging.
/// Demonstrates the use of Debby elements and module lifecycle methods.
/// </summary>
public class NotepadModule : DebbyModule
{
    // Module configuration
    public override             string     Name               => "Notepad";
    public override             DebbyIcon  Icon               => DebbyIcon.FloppyDisk;
    protected internal override Vector2Int DesktopDefaultSize => new(800, 600);
    protected internal override Vector2Int DesktopMinSize     => new(400, 300);

    // PlayerPrefs keys
    private const string NOTES_KEY     = "DebbyNotepad_Notes";
    private const string AUTOSAVE_KEY  = "DebbyNotepad_AutoSave";
    private const string TIMESTAMP_KEY = "DebbyNotepad_Timestamp";

    // UI elements
    private Label       _statusLabel;
    private DebbyButton _saveButton;
    private DebbyButton _clearButton;

    // State
    private readonly AdjustableValue<string> _notes    = new(string.Empty);
    private readonly AdjustableValue<bool>   _autoSave = new(true);
    private          bool                    _hasUnsavedChanges;

    /// <summary>
    /// Called once when the module is first opened.
    /// This is where we build the module's UI.
    /// </summary>
    protected override VisualElement OnModuleCreate()
    {
        // Create root container
        var root = new ScrollView(ScrollViewMode.Vertical);

        // Settings category
        var settingsCategory = new DebbyCategory("SETTINGS");
        root.Add(settingsCategory);
        // NOTE: When creating UI Toolkit layouts from code, it can be convenient
        // to use empty statement blocks {} to show the hierarchy / tree view clearer.
        {
            // Auto-save toggle
            _autoSave.value = PlayerPrefs.GetInt(AUTOSAVE_KEY, 1) == 1;
            var autoSaveToggle = new DebbyToggle("Auto-save notes");
            _autoSave.BindTwoWay(autoSaveToggle);
            _autoSave.OnValueChanged += OnAutoSaveChanged;
            settingsCategory.Add(autoSaveToggle);
        }

        // Notes category
        var notesCategory = new DebbyCategory("NOTES");
        root.Add(notesCategory);
        {
            // Notes text field
            // You can also bind an AdjustableValue to any Debby control, instead of
            // using a value change callback.
            var notesField = new DebbyTextField("Note Content");
            notesField.style.height = 300;
            _notes.BindTwoWay(notesField);
            _notes.OnValueChanged += OnNotesChanged;
            notesCategory.Add(notesField);

            // Actions group
            var actionsGroup = new DebbyGroup("Actions");
            notesCategory.Add(actionsGroup);
            {
                // Save button
                _saveButton = new DebbyButton("Save Notes", DebbyIcon.FloppyDisk);
                _saveButton.Clicked += SaveNotes;
                actionsGroup.Add(_saveButton);

                // Clear button
                _clearButton = new DebbyButton("Clear Notes", DebbyIcon.Trash);
                _clearButton.Clicked += ClearNotes;
                actionsGroup.Add(_clearButton);

                // Export button
                var exportButton = new DebbyButton("Copy to Clipboard", DebbyIcon.Share);
                exportButton.Clicked += CopyToClipboard;
                actionsGroup.Add(exportButton);
            }
        }

        // Info category
        var infoCategory = new DebbyCategory("INFO");
        root.Add(infoCategory);
        {
            // Status label
            _statusLabel = new Label("No notes saved yet")
            {
                style =
                {
                    color = new Color(0.7f, 0.7f, 0.7f),
                    unityFontStyleAndWeight = FontStyle.Italic,
                    paddingLeft = 8,
                    paddingTop = 4,
                },
            };
            infoCategory.Add(_statusLabel);
        }

        return root;
    }

    /// <summary>
    /// Called every time the module is opened.
    /// We use this to load the saved notes.
    /// </summary>
    protected override void OnModuleOpen()
    {
        // Load notes from PlayerPrefs
        LoadNotes();

        // Reset unsaved changes flag
        _hasUnsavedChanges = false;
        UpdateSaveButtonState();
    }

    /// <summary>
    /// Called every time the module is closed.
    /// We use this to auto-save if enabled.
    /// </summary>
    protected override void OnModuleClose()
    {
        // Auto-save if enabled and there are unsaved changes
        if (_autoSave && _hasUnsavedChanges)
        {
            SaveNotes();
        }
    }

    /// <summary>
    /// Called when Debby is destroyed.
    /// Clean up any resources here if needed.
    /// </summary>
    protected override void OnModuleDestroy()
    {
        // Nothing to clean up in this example
        // But you would unregister event handlers, dispose resources, etc. here
    }

    #region Event Handlers

    private void OnAutoSaveChanged(bool value)
    {
        PlayerPrefs.SetInt(AUTOSAVE_KEY, value ? 1 : 0);
        PlayerPrefs.Save();

        UpdateSaveButtonState();

        Debug.Log($"Notepad auto-save {(value ? "enabled" : "disabled")}");
    }

    private void OnNotesChanged(string str)
    {
        _hasUnsavedChanges = true;
        UpdateSaveButtonState();

        // Auto-save if enabled
        if (_autoSave)
        {
            SaveNotes();
        }
    }

    #endregion

    #region Actions

    private void LoadNotes()
    {
        var notes = PlayerPrefs.GetString(NOTES_KEY, string.Empty);
        _notes.SetValueWithoutNotify(notes);

        if (PlayerPrefs.HasKey(TIMESTAMP_KEY))
        {
            var timestamp = PlayerPrefs.GetString(TIMESTAMP_KEY);
            _statusLabel.text = $"Last saved: {timestamp}";
        }
        else
        {
            _statusLabel.text = "No notes saved yet";
        }
    }

    private void SaveNotes()
    {
        PlayerPrefs.SetString(NOTES_KEY, _notes);
        PlayerPrefs.SetString(TIMESTAMP_KEY, System.DateTime.Now.ToString("g"));
        PlayerPrefs.Save();

        _hasUnsavedChanges = false;
        UpdateSaveButtonState();

        var timestamp = PlayerPrefs.GetString(TIMESTAMP_KEY);
        _statusLabel.text = $"Last saved: {timestamp}";

        Debug.Log("Notepad saved successfully");
    }

    private void ClearNotes()
    {
        // Ask for confirmation by logging
        if (_notes.IsNullOrEmpty())
        {
            Debug.LogWarning("Clearing notepad notes...");
        }

        _notes.value = string.Empty;
        _hasUnsavedChanges = true;
        UpdateSaveButtonState();

        if (_autoSave)
        {
            SaveNotes();
        }
    }

    private void CopyToClipboard()
    {
        if (_notes.IsNullOrEmpty())
        {
            Debby.ShowToast(DebbyToastType.Error, "No notes to copy");
            return;
        }

        GUIUtility.systemCopyBuffer = _notes;

        Debby.ShowToast(DebbyToastType.Success, "Notes copied to clipboard");
    }

    private void UpdateSaveButtonState()
    {
        // Disable save button if auto-save is on or no unsaved changes
        var shouldEnableSave = !_autoSave && _hasUnsavedChanges;
        _saveButton.SetEnabled(shouldEnableSave);
    }

    #endregion
}