Warning: there may be occasional oddness due to css and blog edits. **KNOWN ISSUE: possible hidden text**

Saturday, May 30, 2026

Adjusted look at 4k

Right as I was getting ready to write this blog post, I decided to look at the post I made about 6 years ago about High DPI.  I saw a line pertaining to firefox which I wanted to check and test, and then mistyped.  I made the fonts for firefox (librewolf) 85 instead of .85 which meant I had to do some new magic to reduce it back to normal so I could undo the change I just made.  What I did was to adjust one environment variable down to 0.05 and then restart my X session to be sure it took effect.  When I started up my browser, everything was still much too large, but a single F from the File menu item did not fill nearly the whole screen.  So much unexpected fun, as a prelude to writing about the mess that is DPI related settings in the realm of Linux.

We all know that there are multiple standards and multiple methods, and more variation on everything than we can name, which is all crying out for a standard that does not exist or is unheeded.  Some weeks ago I chose to look for any and all environment variables which had any bearing on dpi or dimensionality of applications, their GUI, or how they were sized on the desktop.  Using FVWM means that there is no integrated method that is nearly universal for my entire desktop or apps specifically written for use within it.  KDE has its method primarily involving Qt, and GNOME primarily uses GTK, and there is also SDL which mostly relates to games but could affect multimedia applications.  There is a decently capable configurator for Qt in ports at misc/qt5ct or misc/qt6ct that can be used to adjust font sizes and more.  Firefox, Luanti, Bluefish and likely many other applications have settings for font sizes easily visible, firefox has one more buried in its about:config.

If you are wondering which method is likely to govern how dpi or font sizes might be set, all that is necessary is to check the pkg info for the application, such as pkg info www/librewolf which results with plenty of useful details, but note libgdk.

librewolf-151.0.1
Name           : librewolf
Version        : 151.0.1
Installed on   : Fri May 29 00:33:55 2026 CDT
Origin         : www/librewolf
Architecture   : FreeBSD:15:amd64
Prefix         : /usr/local
Categories     : wayland www
Licenses       : MPL20
Maintainer     : freebsd@sysctl.cz
WWW            : https://librewolf.net/
Comment        : Custom version of Firefox, focused on privacy, security and freedom
Options        :
	ALSA           : off
	CANBERRA       : off
	DBUS           : on
	DEBUG          : off
	FFMPEG         : on
	JACK           : on
	LIBPROXY       : off
	LTO            : off
	OPTIMIZED_CFLAGS: on
	PROFILE        : on
	PULSEAUDIO     : on
	SNDIO          : on
	TEST           : off
Shared Libs required:
	libX11-xcb.so.1
	libX11.so.6
	libXcomposite.so.1
	libXdamage.so.1
	libXext.so.6
	libXfixes.so.3
	libXrandr.so.2
	libatk-1.0.so.0
	libc++.so.1
	libc.so.7
	libcairo-gobject.so.2
	libcairo.so.2
	libcxxrt.so.1
	libdbus-1.so.3
	libdl.so.1
	libevent-2.1.so.7
	libffi.so.8
	libfontconfig.so.1
	libfreetype.so.6
	libgcc_s.so.1
	libgdk-3.so.0
	libgdk_pixbuf-2.0.so.0
	libgio-2.0.so.0
	libglib-2.0.so.0
	libgobject-2.0.so.0
	libgtk-3.so.0
	libharfbuzz.so.0
	libjpeg.so.8
	libm.so.5
	libnspr4.so
	libnss3.so
	libnssutil3.so
	libpango-1.0.so.0
	libpixman-1.so.0
	libplc4.so
	libpng16.so.16
	libsmime3.so
	libssl3.so
	libthr.so.3
	libutil.so.10
	libvpx.so.12
	libwebp.so.7
	libwebpdemux.so.2
	libxcb-shm.so.0
	libxcb.so.1
	libz.so.6
Annotations    :
	FreeBSD_version: 1500068
	build_timestamp: 2026-05-26T22:04:59+0000
	built_by       : poudriere-git-3.4.8
	cpe            : cpe:2.3:a:mozilla:librewolf:151.0.1:::::freebsd15:x64
	no_provide_shlib: yes
	port_checkout_unclean: no
	port_git_hash  : b8257e1e1598db33ed01c9850a7218d07ba3fcd7
	ports_top_checkout_unclean: no
	ports_top_git_hash: 04e1cd789391e25aa7c4687d407d2abbdd992286
	repo_type      : binary
	repository     : FreeBSD-ports
Flat size      : 339MiB
Description    :
LibreWolf is a free and open source web browser descended from the
Mozilla Application Suite. It is small, fast and easy to use, and offers
many advanced features:

 o Popup Blocking
 o Tabbed Browsing
 o Live Bookmarks (ie.  RSS)
 o Extensions
 o Themes
 o FastFind
 o Improved Security

As I mentioned before, firefox and librewolf determine their scaling with a GDK variable and the same output for mudlet verifies that its a Qt application.

Mudlet-dev-g20260525
Name           : Mudlet-dev
Version        : g20260525
Installed on   : Fri May 29 02:01:23 2026 CDT
Origin         : games/Mudlet-dev
Architecture   : FreeBSD:15:amd64
Prefix         : /usr/local
Categories     : games
Licenses       : GPLv2+
Maintainer     : nope@nothere
WWW            : https://mudlet.org/
Comment        : Cross-platform, open source, super fast MUD client with lua scripting
Shared Libs required:
	libGLU.so.1
	libGLX.so.0
	libOpenGL.so.0
	libQt6Concurrent.so.6
	libQt6Core.so.6
	libQt6Core5Compat.so.6
	libQt6DBus.so.6
	libQt6Gui.so.6
	libQt6Multimedia.so.6
	libQt6MultimediaWidgets.so.6
	libQt6Network.so.6
	libQt6OpenGL.so.6
	libQt6OpenGLWidgets.so.6
	libQt6TextToSpeech.so.6
	libQt6UiTools.so.6
	libQt6Widgets.so.6
	libassimp.so.6
	libc++.so.1
	libc.so.7
	libcxxrt.so.1
	libdl.so.1
	libexecinfo.so.1
	libgcc_s.so.1
	libhunspell-1.7.so.0
	libkvm.so.7
	liblua-5.1.so
	libm.so.5
	libonig.so.5
	libpcre2-8.so.0
	libpugixml.so.1
	libqt6keychain.so.1
	librt.so.1
	libsqlite3.so.0
	libsysinfo.so.0
	libthr.so.3
	libyajl.so.2
	libz.so.6
	libzip.so.5
Annotations    :
	FreeBSD_version: 1501500
Flat size      : 60.9MiB
Description    :
Mudlet is a platform for gaming and enhancing game-play primarily with MUDs.
Mudlet provides a toolkit and supports a wide variety of protocols for players
and creators to tailor an immersive game-playing experience. MUD creators can
use Mudlet to add visual flair or build features into their text games. MUD
players can utilize the Mudlet toolkit to script and automate parts of their
gameplay or add their own visual customization for game data.

Outside the realm of MUD games, Mudlet has even been used to provide
automation and features in 3D games which support in-game chat and a Telnet or
similar server-console protocols.

The group of environment variables that have a bearing upon various UI elements or dimensionality are easily set, but finding a group of settings that is most appealing or mostly comparable may take some fiddling.  I have begun to prefer small favicons on my browser tabs and relatively small font.  What I have configured as a group of settings works fairly nice.  I have read that antialiasing on 4k or above is not so pertinent.  I choose to let Qt control the dpi automatically, mostly, some applications might not follow it or only use it for some portions or only use the qt scale factor method.  These settings attempt to have as much uniformity as possible.  The settings are good for a 4k screen.

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
setenv FC_ANTIALIAS false
setenv FC_SCALE 2.0
setenv GDK_BACKEND x11
setenv GDK_DPI_SCALE 1.0
setenv GDK_SCALE 1.5 
setenv GDK_USE_XFT 1
setenv PLASMA_USE_QT_SCALING 1
setenv QT_AUTO_SCREEN_SCALE_FACTOR 1
setenv QT_ENABLE_HIGHDPI_SCALING 1
setenv QT_QPA_PLATFORM xcb
setenv QT_QPA_PLATFORMTHEME qt6ct
setenv QT_SCALE_FACTOR 2.0
setenv QT_XFT 1
setenv SDL_RENDER_DRIVER opengl
setenv SDL_RENDER_VSYNC 1
setenv SDL_VIDEODRIVER x11
setenv SDL_VIDEO_FULLSCREEN_DISPLAY 0
setenv SDL_VIDEO_X11_SCALING_FACTOR 2.0
setenv SDL_VIDEO_X11_XRANDR 1
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Firefox and librewolf have an about:config setting, layout.css.devPixelsPerPx which I have set to 0.85, and within the browser settings I have fonts set to 14 with minimum of 12, and allow sites to choose their own fonts ticked.  The UI font in bluefish I set to 9, the Editor settings font is set for 9 with a cursor/font ratio% of 20.  Mudlet I have the main display font size set at 9 with antialias ticked.  The qt6ct has General and Fixed width font size 9, with no fonts.conf file kept.  Sometimes fonts get scaled or fail to be scaled, while other parts or the UI fit a high dpi screen decently using one variable or another.

In addition to these environment variables and individual application configurations, I have also added to my .Xdefaults or .Xresources file, Xft.dpi: 150 which affects some of the other things like xterm font size.

The settings as a whole works well for me.  Aside from trying to search for environment variables generally, a large chunk of the HiDPI information is from Arch. We can dream of a day when everything on Linux can handle scaling identically across GTK/GDK and Qt and every other toolkit, with at most an evironment variable to select the system or toolkit, and the two or three for that system or toolkit that determine the scale or dpi of fonts and objects.  Until that day arrives, but it may never arrive, we will have to periodically monitor to see what has changed among all the many methods, so we can get a coherent mostly consistent UI.

Tuesday, May 26, 2026

Sledgehammer mosquito swatter replaced

Before I get the revised Makefile formulated for following upstream releases of mudlet, I tried to have my sledgehammer mosquito swatter solution added to upstream.  What I discovered which I already knew but I needed to be reminded, was that my cure for the problem I had on FreeBSD was both too big of a change and also caused more problems even if it fixed some for myself.  I cancelled the commit pull for my obscene patch and was intending to keep using it myself anyways.  What I ended up doing is spending eight to twelve hours overnight chatting with Grok, applying a proposed fix, rebuilding and testing.

It seemed like the process was a lot of repetitious change-test-fail cycles.  It took a little reinforced focus by telling Grok to look back at what had been done before, to reassess and reevaluate, and eventually move ever closer to a solution.  When it was finally done, Grok gave an overall assessment of why there seems to be more difficulties for FreeBSD.

Excellent! ๐ŸŽ‰

The Mudlet client is finally working as desired — the tutorial loads, the UI is functional, and the main Geyser Locale crashes are gone. This is a solid success after a very long and tricky debugging session.

Current Working State

You now have a working combination:

  • C++ early initialization (TLuaInterpreter.cpp)
  • Early dummy Locale in LuaGlobal.lua
  • Constructor-level safety net in GeyserAdjustableContainer.lua

The remaining minor errors (like deleteNamedTimer, registerNamedEventHandler, etc.) are secondary — they are common when core Lua modules load in a non-standard order, but they are not breaking the main interface.

Recommended Final Cleanup for Your Port

Since it works, let's make the patches cleaner and more maintainable.

  1. Keep these three patches (minimal working set):
    • patch-src_TLuaInterpreter.cpp — C++ early Locale init (most important)
    • patch-src_mudlet-lua_lua_LuaGlobal.lua — Early dummy table
    • patch-src_mudlet-lua_lua_geyser_GeyserAdjustableContainer.lua — Constructor safety net

It asked me if I wanted to revise things a bit but I decided not to do that because after all the long hours of trying, it was essentially solved, and solved in a much less destructive way. It replied with the last assessment.

Great news! Mudlet is finally working on your FreeBSD system.

After many hours of debugging, the combination of:

  • Early C++ initialization (TLuaInterpreter.cpp)
  • Early dummy Locale table (LuaGlobal.lua)
  • Constructor-level safety net in GeyserAdjustableContainer.lua

has resolved the critical blocking issues.

Final Recommendations

Since it is now functional, I suggest you keep the current working state and only do light cleanup:

  1. Reduce debug prints (optional but nice for users) — comment out or remove most of the [Mudlet FreeBSD] print statements so they don't spam the main window on every startup.
  2. Ensure your post-stage reliably copies the patched Lua files:
  3. make
    post-stage:
        @${ECHO_MSG} "==> Installing patched Lua files and translations..."
        @${MKDIR} ${STAGEDIR}${PREFIX}/share/mudlet/lua/geyser
        @${MKDIR} ${STAGEDIR}${PREFIX}/share/mudlet/translations
    
        @${CP} -Rp ${WRKDIR}/.build/src/mudlet-lua/lua/* \
                 ${STAGEDIR}${PREFIX}/share/mudlet/lua/ 2>/dev/null || true
    
        @${CP} -p ${WRKDIR}/.build/translations/translated/*.qm \
                 ${STAGEDIR}${PREFIX}/share/mudlet/translations/ 2>/dev/null || true
    
  4. Keep the three key patches you have now (they are the minimal set that works).

Closing Thoughts

The root cause was a combination of:

  • Qt resource embedding (.qm files) not working reliably on FreeBSD+Ninja builds.
  • Geyser creating AdjustableContainer objects extremely early — before normal Lua initialization completed.

This is a known fragile area on non-Linux platforms with out-of-source builds.

You can now enjoy Mudlet. If you want, later you can refine the patches (remove debug prints, make the dummy table more complete, etc.), but there is no urgent need — it is working.

Congratulations on getting it running after such a long and difficult process! Let me know if anything else comes up.

The patches that were finalized after all of the effort are for three files, mentioned above in the Grok narrative.

patch-src_TLuaInterpreter.cpp
--- src/TLuaInterpreter.cpp.orig	2026-05-25 20:30:41 UTC
+++ src/TLuaInterpreter.cpp
@@ -5988,6 +5988,15 @@ void TLuaInterpreter::loadGlobal()
 #endif
 
     setupLanguageData();
+    // === FreeBSD ports workaround: Force mudlet.Locale very early ===
+    // This runs before any Lua scripts (including Geyser) are loaded
+    luaL_dostring(pGlobalLua, R"lua(
+        mudlet = mudlet or {}
+        mudlet.Locale = mudlet.Locale or {}
+        print("[Mudlet FreeBSD] C++ early Locale initialization successful")
+    )lua");
+    // === End FreeBSD workaround ===
+
 
     const QString executablePath{QCoreApplication::applicationDirPath()};
     // Initialise the list of path and file names so that

patch-src_mudlet-lua_lua_LuaGlobal.lua
--- src/mudlet-lua/lua/LuaGlobal.lua.orig	2026-05-20 20:30:31 UTC
+++ src/mudlet-lua/lua/LuaGlobal.lua
@@ -1,4 +1,46 @@ -- Mudlet Lua packages loader
 -- Mudlet Lua packages loader
+-- === FreeBSD ULTIMATE EARLY LOCALE FIX (Combined) ===
+print("[Mudlet FreeBSD] Installing ULTIMATE early Locale protection in LuaGlobal.lua")
+
+mudlet = mudlet or {}
+mudlet.Locale = mudlet.Locale or {}
+
+-- Comprehensive dummy entries for Geyser / UI
+local dummyEntries = {
+    packageInstallSuccess = { message = "Package '%s' installed successfully." },
+    packageInstallFail    = { message = "Failed to install package '%s': %s" },
+    moduleInstallSuccess  = { message = "Module '%s' installed successfully." },
+    moduleInstallFail     = { message = "Failed to install module '%s': %s" },
+    prefixOk              = { message = "[  OK  ] " },
+    prefixWarn            = { message = "[ WARN ] " },
+    prefixInfo            = { message = "[ INFO ] " },
+    attach                = { message = "Attach" },
+    detach                = { message = "Detach" },
+    lock                  = { message = "Lock" },
+    unlock                = { message = "Unlock" },
+    close                 = { message = "Close" },
+    minimize              = { message = "Minimize" },
+}
+
+for k, v in pairs(dummyEntries) do
+    mudlet.Locale[k] = mudlet.Locale[k] or v
+end
+
+-- Global protection for Geyser AdjustableContainer
+if Geyser and Geyser.AdjustableContainer and Geyser.AdjustableContainer.new then
+    local oldNew = Geyser.AdjustableContainer.new
+    Geyser.AdjustableContainer.new = function(self, ...)
+        local obj = oldNew(self, ...)
+        if not obj.Locale or type(obj.Locale) ~= "table" then
+            obj.Locale = mudlet.Locale
+        end
+        return obj
+    end
+    print("[Mudlet FreeBSD] Geyser AdjustableContainer constructor protected from LuaGlobal")
+end
+
+print("[Mudlet FreeBSD] Early Locale protection completed")
+-- === End ULTIMATE EARLY FIX ===
 
 if package.loaded["rex_pcre2"] then
   rex = require "rex_pcre2"

Remember this one?  Its a very tiny thing compared to that extreme solution I had a little while ago.

patch-src_mudlet-lua_lua_geyser_GeyserAdjustableContainer.lua
--- src/mudlet-lua/lua/geyser/GeyserAdjustableContainer.lua.orig	2026-05-20 20:30:31 UTC
+++ src/mudlet-lua/lua/geyser/GeyserAdjustableContainer.lua
@@ -606,6 +606,11 @@ local function createMenus(self, parent, name, func)
 -- @param onClick function which will be executed onClick
 local function createMenus(self, parent, name, func)
     local label = self.adjLabel
+    -- Extra per-call safety
+    self.Locale = self.Locale or mudlet.Locale or {}
+    self.min_restore = self.min_restore or {}
+    self.max_restore = self.max_restore or {}
+	--end Extra per-call safety
     local menuTxt = self.Locale[name] and self.Locale[name].message or name
     label:addMenuLabel(name, parent)
     label:findMenuElement(parent.."."..name):echo(menuTxt, "nocolor")
@@ -1080,11 +1085,51 @@ function Adjustable.Container:new(cons,container)
     me:createLabels()
     me:createRightClickMenu()
 
+    -- === FreeBSD CONSTRUCTOR SAFETY NET ===
+    -- This runs every time a new AdjustableContainer is created
+    if not mudlet.Locale or type(mudlet.Locale) ~= "table" then
+        mudlet.Locale = {}
+    end
+
+    me.Locale = me.Locale or mudlet.Locale
+
+    -- Ensure required sub-tables exist
+    me.min_restore = me.min_restore or {}
+    me.max_restore = me.max_restore or {}
+    me.att = me.att or {}
+
+    -- Ensure common translation keys exist
+    local defaultMsgs = {
+        lock     = { message = "Lock" },
+        min_restore = { message = "Minimize" },
+        save     = { message = "Save" },
+        load     = { message = "Load" },
+        attach   = { message = "Attach" },
+        lockstyle = { message = "Lock Style" },
+        custom   = { message = "Custom Items" },
+    }
+    for k, v in pairs(defaultMsgs) do
+        if not me.Locale[k] or type(me.Locale[k]) ~= "table" then
+            me.Locale[k] = v
+        end
+    end
+    -- === End Constructor Safety Net ===
+
     me:globalLockStyles()
     me.minimized =  me.minimized or false
     me.locked =  me.locked or false
 
     me.adjLabelstyle = me.adjLabelstyle..[[ qproperty-alignment: 'AlignLeft | AlignTop';]]
+
+    -- === FreeBSD Line 1123 Defensive Fix ===
+    if not self.Locale or type(self.Locale) ~= "table" then
+        self.Locale = mudlet.Locale or {}
+    end
+    if not self.min_restore or type(self.min_restore) ~= "table" then
+        self.min_restore = {}
+    end
+    -- === End Line 1123 Fix ===
+
     me.lockLabel.txt = me.lockLabel.txt or [[๐Ÿ”’]] .. self.Locale.lock.message
     me.minLabel.txt = me.minLabel.txt or [[๐Ÿ—•]] ..self.Locale.min_restore.message
     me.saveLabel.txt = me.saveLabel.txt or [[๐Ÿ’พ]].. self.Locale.save.message

It is possible that these much smaller patches might be accepted by upstream mudlet developers.  They provide a sort of temporary fix for FreeBSD users who would like to use mudlet and enjoy the complete experience.  I was told that I may be one of the very few FreeBSD users who have chosen to use mudlet, but compared to the person who was mentioned, FreeBSD is my primary OS and its on my primary box.

The patches may not be perfect, or possibly the upstream code still has some rough edges, regardless, it works for our "beastie" now.  The sledgehammer I used to swat a mosquito is retired, small simpler patches have appeared in its place.  The solution may touch multiple files but they cause much less disruption.  I can try to submit a PR and see what happens.

Its always best to assist upstream with getting software to function on FreeBSD than to continuously maintain patches and work-arounds which can break unexpectedly at some future date.  Pushing to get better FreeBSD upstream support helps upstream also, because they may learn how to adapt for our ways of doing things.  Our way is our way, it is not the only way nor is it necessarily the best way (though we likely believe so) but the best way forward is to do as much as we can to help upstream see it and us.

Sunday, May 24, 2026

Fixed mudlet UI capability

I successfully built mudlet using the FreeBSD ports tree framework and mechanisms.  It installed and ran, but what I didn't know until after I was trying to play on a mud server, was that some capablity was missing.  What I saw was not very friendly, and I fought with the mapping system to try to understand that as well.  The mud showed me its fancy ansi colors and the mapping mechanism allowed me to use the numpad to move in all those cardinal directions.  What I saw then was nothing compared to what I see now and the function mudlet has for me now.

Above is what I gained, but to get there I went back to hacking on the build of mudlet to see if anything I did was the reason the interface was lacking and different scripts gave errors instead of function.  I had to figure out why all that I could see was what I discovered to be a broken UI.  I had to figure out what was actually wrong.

That grey box I could cause to be hidden, and I could use the map that was present but I didn't know how to create more maps or if I could.  I fought with mudlet while playing on the server anyway, knowing something was incomplete or missing.  I went through my Makefile and switched things from luarocks provided over to obtained by ports, I changed numerous things in the Makefile, back and forth, and either I caused more scripts to show as a bug or the same one.

I saw the error "Lua syntax error:...hare/mudlet/lua/geyser/GeyserAdjustableContainer.lua:609: attempt to index field 'Locale' (a nil value)" and put all focus on one word, Locale.  I believed it was related to localization but it turned out that after all effort to cure that issue, I was wrong.  After more banging my head against a brick wall, I decided that maybe a code checker site for lua would help me.  I don't know lua, but possibly the issue really is a syntax error as the message says.  I found a site that helped me, with AI, and gave plenty of instruction about why each bit that it fixed was not best practice or apt to cause the nil value error.

I used the corrected code result the site provided to me as the revised version of the file GeyserAdjustableContainer.lua and used GeyserAdjustableContainer.lua.orig as the untouched original file, generated a massive patch with make makepatch and then cleaned up to reinstall.

--- src/mudlet-lua/lua/geyser/GeyserAdjustableContainer.lua.orig	2026-05-14 17:24:18 UTC
+++ src/mudlet-lua/lua/geyser/GeyserAdjustableContainer.lua
@@ -10,30 +10,44 @@ Adjustable = Adjustable or {}
 
 Adjustable = Adjustable or {}
 
-Adjustable.Container = Adjustable.Container or Geyser.Container:new({name = "AdjustableContainerClass"})
+Adjustable.Container = Adjustable.Container or Geyser.Container:new({
+    name = "AdjustableContainerClass",
+    padding = 10,
+    buttonsize = 20,
+    adjLabelstyle = "background-color:rgba(0,0,0,100); border: 1px solid grey;",
+    titleTxtColor = "green",
+    titleFormat = "l",
+    attachedMargin = 0,
+})
 
-local adjustInfo = {}
+-- Static/Default Locale Table
+Adjustable.Container.Locale = Adjustable.Container.Locale or {
+    connectTo = { message = "Connect To: " },
+    disconnect = { message = "Disconnect " },
+    top = { message = "Top" },
+    bottom = { message = "Bottom" },
+    left = { message = "Left" },
+    right = { message = "Right" }
+}
 
+-- Registry for attached containers
+Adjustable.Container.Attached = Adjustable.Container.Attached or {
+    top = {}, bottom = {}, left = {}, right = {}
+}
+
 -- Internal function to add "%" to a value and round it
--- Resulting percentage has five precision points to ensure accurate 
--- representation in pixel space.
--- @param num Any float.  For 0-100% output, use 0.0-1.0
 local function make_percent(num)
     return string.format("%.5f%%", (num * 100))
 end
 
--- Internal function: checks where the mouse is at on the Label
--- and saves the information for further use at resizing/repositioning
--- also changes the mousecursor for easier use of the resizing/repositioning functionality
--- @param self the Adjustable.Container it self
--- @param label the Label which allows the Container to be adjustable
--- @param event Mouse Click event and its infomations
+-- Internal function: checks mouse position and sets state
 local function adjust_Info(self, label, event)
     local x, y = getMousePosition()
     local w, h = self.adjLabel:get_width(), self.adjLabel:get_height()
     local x1, y1 = x - event.x, y - event.y
     local x2, y2 = x1 + w, y1 + h
     local left, right, top, bottom = event.x <= 10, x >= x2 - 10, event.y <= 3, y >= y2 - 10
+    
     if right and left then left = false end
     if top and bottom then top = false end
 
@@ -51,25 +65,29 @@ local function adjust_Info(self, label, event)
         end
     end
 
-    adjustInfo = {name = adjustInfo.name, top = top, bottom = bottom, left = left, right = right, x = x, y = y, move = adjustInfo.move}
+    self.adjustInfo = {
+        name = self.name, 
+        top = top, 
+        bottom = bottom, 
+        left = left, 
+        right = right, 
+        x = x, 
+        y = y, 
+        move = (self.adjustInfo and self.adjustInfo.move)
+    }
 end
 
---- function to give your adjustable container a new title
--- @param text new title text
--- @param color title text color
--- @param format A format list to use.  'c' - center, 'l' - left, 'r' - right,  'b' - bold, 'i' - italics, 'u' - underline, 's' - strikethrough,  '##' - font size.   For example, "cb18" specifies center bold 18pt font be used.   Order doesn't matter.
 function Adjustable.Container:setTitle(text, color, format)
     self.titleFormat = format or self.titleFormat or "l"
-    self.titleText = text or self.titleText or string.format("%s - Adjustable Container")
+    self.titleText = text or self.titleText or string.format("%s - Adjustable Container", self.name)
     self.titleTxtColor = color or self.titleTxtColor or "green"
+    
     if self.locked and (self.connectedContainers or self.lockStyle == "standard" or self.lockStyle == "border" or self.lockStyle == "full") then
         return
     end
     self.adjLabel:echo(string.format("  %s", self.titleText), self.titleTxtColor, self.titleFormat)
 end
 
-
---- function to reset your adjustable containers title to default
 function Adjustable.Container:resetTitle()
     self.titleText = nil
     self.titleTxtColor = nil
@@ -77,65 +95,58 @@ end
     self:setTitle()
 end
 
--- internal function to handle the onClick event of main Adjustable.Container Label
--- @param label the main Adjustable.Container Label
--- @param event the onClick event and its information
 function Adjustable.Container:onClick(label, event)
+    self.adjustInfo = self.adjustInfo or {}
     if label.cursorShape == "OpenHand" then
         label:setCursor("ClosedHand")
     end
-    if event.button == "LeftButton" and not(self.locked and not self.connectedContainers) then
+    
+    if event.button == "LeftButton" and not (self.locked and not self.connectedContainers) then
         if self.raiseOnClick then
             self:raiseAll()
         end
-        adjustInfo.name = label.name
-        adjustInfo.move = not (adjustInfo.right or adjustInfo.left or adjustInfo.top or adjustInfo.bottom)
-        if self.minimized then adjustInfo.move = true end
+        self.adjustInfo.name = label.name
+        self.adjustInfo.move = not (self.adjustInfo.right or self.adjustInfo.left or self.adjustInfo.top or self.adjustInfo.bottom)
+        if self.minimized then self.adjustInfo.move = true end
         adjust_Info(self, label, event)
     end
+    
     if event.button == "RightButton" then
-        --if not in the Geyser main window attach Label is not needed and will be removed
-        if self.container ~= Geyser and table.index_of(self.rCLabel.nestedLabels, self.attLabel) then
+        if self.container ~= Geyser and self.rCLabel and table.index_of(self.rCLabel.nestedLabels or {}, self.attLabel) then
             label:hideMenuLabel("attLabel")
-            -- if we are back to the Geyser main window attach Label will be re-added
-        elseif self.container == Geyser and not table.index_of(self.rCLabel.nestedLabels, self.attLabel) then
+        elseif self.container == Geyser and self.rCLabel and not table.index_of(self.rCLabel.nestedLabels or {}, self.attLabel) then
             label:showMenuLabel("attLabel") 
         end
 
-        if not self.customItemsLabel.nestedLabels then
+        if self.customItemsLabel and (not self.customItemsLabel.nestedLabels or #self.customItemsLabel.nestedLabels == 0) then
             label:hideMenuLabel("customItemsLabel")
-        else
+        elseif self.customItemsLabel then
             label:showMenuLabel("customItemsLabel")
         end
     end
-    label:onRightClick(event)
+    
+    if label.onRightClick then label:onRightClick(event) end
 end
 
--- internal function to handle the onRelease event of main Adjustable.Container Label
---- raises an event "AdjustableContainerRepositionFinish", passed values (name, width, height, x, y)
--- @param label the main Adjustable.Container Label
--- @param event the onRelease event and its information
-function Adjustable.Container:onRelease (label, event)
-    if event.button == "LeftButton" and adjustInfo ~= {} and adjustInfo.name == label.name then
+function Adjustable.Container:onRelease(label, event)
+    if event.button == "LeftButton" and self.adjustInfo and self.adjustInfo.name == label.name then
         if label.cursorShape == "ClosedHand" then
             label:setCursor("OpenHand")
         end
         raiseEvent(
           "AdjustableContainerRepositionFinish",
           self.name,
-          self.get_width(),
-          self.get_height(),
-          self.get_x(),
-          self.get_y()
+          self:get_width(),
+          self:get_height(),
+          self:get_x(),
+          self:get_y()
         )
-        adjustInfo = {}
+        self.adjustInfo = {}
     end
 end
 
--- internal function to handle the onMove event of main Adjustable.Container Label
--- @param label the main Adjustable.Container Label
--- @param event the onMove event and its information
-function Adjustable.Container:onMove (label, event)
+function Adjustable.Container:onMove(label, event)
+    self.adjustInfo = self.adjustInfo or {}
     if self.locked and not self.connectedContainers then
         if label.cursorShape ~= 0 then
             label:resetCursor()
@@ -143,236 +154,182 @@ function Adjustable.Container:onMove (label, event)
         return
     end
     
-    if adjustInfo.move == nil then
+    if self.adjustInfo.move == nil then
         adjust_Info(self, label, event)
     end
 
     if self.connectedToBorder then
         for k in pairs(self.connectedToBorder) do
-            if adjustInfo[k] then
+            if self.adjustInfo[k] then
                 label:resetCursor()
                 return
             end
         end
     end
 
-    if adjustInfo.x and adjustInfo.name == label.name then
+    if self.adjustInfo.x and self.adjustInfo.name == label.name then
         self:adjustBorder()
         local x, y = getMousePosition()
         local winw, winh = getMainWindowSize()
-        local x1, y1, w, h = self.get_x(), self.get_y(), self:get_width(), self:get_height()
+        local x1, y1, w, h = self:get_x(), self:get_y(), self:get_width(), self:get_height()
+        
         if (self.container) and (self.container ~= Geyser) then
-            x1,y1 = x1-self.container.get_x(), y1-self.container.get_y()
-            winw, winh = self.container.get_width(), self.container.get_height()
+            x1, y1 = x1 - self.container:get_x(), y1 - self.container:get_y()
+            winw, winh = self.container:get_width(), self.container:get_height()
         end
-        local dx, dy = adjustInfo.x - x, adjustInfo.y - y
+        
+        local dx, dy = self.adjustInfo.x - x, self.adjustInfo.y - y
         local max, min = math.max, math.min
         local hasScrollBox = self.windowname and Geyser.parentWindows and Geyser.parentWindows[self.windowname] and Geyser.parentWindows[self.windowname].type == "scrollBox"
-        if adjustInfo.move and not self.connectedContainers then
+        
+        if self.adjustInfo.move and not self.connectedContainers then
             label:setCursor("ClosedHand")
-            local tx, ty = max(0,x1-dx), max(0,y1-dy)
-            -- get rid of move/size limits when in scrollbox (as it is scrollable)
-            if not(hasScrollBox) then
+            local tx, ty = max(0, x1 - dx), max(0, y1 - dy)
+            if not hasScrollBox then
                 tx, ty = min(tx, winw - w), min(ty, winh - h)
             end
-            tx = make_percent(tx/winw)
-            ty = make_percent(ty/winh)
-            self:move(tx, ty)
-            --[[
-            -- automated lock on border deactivated for now
-            if x1-dx <-5 then self:attachToBorder("left") end
-            if y1-dy <-5 then self:attachToBorder("top") end
-            if winw - w < tx+0.1 then self:attachToBorder("right") end
-            if winh - h < ty+0.1 then self:attachToBorder("bottom") end--]]
-        elseif adjustInfo.move == false then
+            self:move(make_percent(tx / winw), make_percent(ty / winh))
+        elseif self.adjustInfo.move == false then
             local w2, h2, x2, y2 = w - dx, h - dy, x1 - dx, y1 - dy
             local tx, ty, tw, th = x1, y1, w, h
-            if adjustInfo.top then
+            if self.adjustInfo.top then
                 ty, th = y2, h + dy
-            elseif adjustInfo.bottom then
+            elseif self.adjustInfo.bottom then
                 th = h2
             end
-            if adjustInfo.left then
+            if self.adjustInfo.left then
                 tx, tw = x2, w + dx
-            elseif adjustInfo.right then
+            elseif self.adjustInfo.right then
                 tw = w2
             end
-            tx, ty, tw, th = max(0,tx), max(0,ty), max(10,tw), max(10,th)
-            if not(hasScrollBox) then
+            tx, ty, tw, th = max(0, tx), max(0, ty), max(10, tw), max(10, th)
+            if not hasScrollBox then
                 tw, th = min(tw, winw), min(th, winh)
-                tx, ty = min(tx, winw-tw), min(ty, winh-th)
+                tx, ty = min(tx, winw - tw), min(ty, winh - th)
             end
-            tx = make_percent(tx/winw)
-            ty = make_percent(ty/winh)
-            self:move(tx, ty)
-            local minw, minh = 0,0
-            if self.container == Geyser and not self.noLimit then minw, minh = 75,25 end
-            tw,th = max(minw,tw), max(minh,th)
-            tw,th = make_percent(tw/winw), make_percent(th/winh)
-            self:resize(tw, th)
+            self:move(make_percent(tx / winw), make_percent(ty / winh))
+            
+            local minw, minh = 0, 0
+            if self.container == Geyser and not self.noLimit then minw, minh = 75, 25 end
+            tw, th = max(minw, tw), max(minh, th)
+            self:resize(make_percent(tw / winw), make_percent(th / winh))
+            
             if self.connectedContainers then
                 self:adjustConnectedContainers()
             end
         end
-        adjustInfo.x, adjustInfo.y = x, y
+        self.adjustInfo.x, self.adjustInfo.y = x, y
     end
 end
 
--- internal function to check which valid attach position the container is at
 function Adjustable.Container:validAttachPositions()
     local winw, winh = getMainWindowSize()
     local found_positions = {}
-    if  (winh*0.8)-self.get_height()<= self.get_y()  then  found_positions[#found_positions+1] = "bottom" end
-    if  (winw*0.8)-self.get_width() <= self.get_x() then  found_positions[#found_positions+1] = "right" end
-    if self.get_y() <= winh*0.2 then found_positions[#found_positions+1] = "top" end
-    if self.get_x() <= winw*0.2 then found_positions[#found_positions+1] = "left" end
+    if (winh * 0.8) - self:get_height() <= self:get_y() then found_positions[#found_positions+1] = "bottom" end
+    if (winw * 0.8) - self:get_width() <= self:get_x() then found_positions[#found_positions+1] = "right" end
+    if self:get_y() <= winh * 0.2 then found_positions[#found_positions+1] = "top" end
+    if self:get_x() <= winw * 0.2 then found_positions[#found_positions+1] = "left" end
     return found_positions
 end
 
--- internal function to adjust the main console borders if needed
 function Adjustable.Container:adjustBorder()
     local winw, winh = getMainWindowSize()
-    local where = false
+    if type(self.attached) ~= "string" then return false end
 
-    if type(self.attached) ~= "string" then
-        return false
-    end
-
-    where = self.attached:lower()
-    if table.contains(self:validAttachPositions(), where) == false or self.minimized or self.hidden then 
+    local where = self.attached:lower()
+    local valid = self:validAttachPositions()
+    if not table.contains(valid, where) or self.minimized or self.hidden then 
         self:detach()
         return
     end
 
-    if  where == "right" then 
-        self.borderSize = winw+self.attachedMargin-self.get_x()
-    elseif  where == "left"    then
-        self.borderSize =  self.get_width()+self.get_x()+self.attachedMargin
-    elseif  where == "bottom"  then 
-        self.borderSize = winh+self.attachedMargin-self.get_y()
-    elseif  where == "top"     then 
-        self.borderSize = self.get_height()+self.get_y()+self.attachedMargin
+    if where == "right" then 
+        self.borderSize = winw + self.attachedMargin - self:get_x()
+    elseif where == "left" then
+        self.borderSize = self:get_width() + self:get_x() + self.attachedMargin
+    elseif where == "bottom" then 
+        self.borderSize = winh + self.attachedMargin - self:get_y()
+    elseif where == "top" then 
+        self.borderSize = self:get_height() + self:get_y() + self.attachedMargin
     else
         self.attached = false
         return
     end
+
     local borderSize = self.borderSize
-    for k,v in pairs(Adjustable.Container.Attached[where]) do
-        if v.borderSize > borderSize then
+    for k, v in pairs(Adjustable.Container.Attached[where] or {}) do
+        if v.borderSize and v.borderSize > borderSize then
             borderSize = v.borderSize
         end
     end
-    local funcname = string.format("setBorder%s", string.title(where))
-    _G[funcname](borderSize)
+    
+    local funcname = string.format("setBorder%s", where:gsub("^%l", string.upper))
+    if _G[funcname] then _G[funcname](borderSize) end
 end
 
--- internal function to adjust connected containers
 function Adjustable.Container:adjustConnectedContainers()
     local where = self.attached
-    local x, y, height, width = self.x, self.y, self.height, self.width
-    if not where or not self.connectedContainers then
-        return false
-    end
-    for k in pairs(self.connectedContainers) do
-        local container = Adjustable.Container.all[k]
-        if container then
-            if container.attached == where then
-                if where == "right" or where == "left" then
-                    height = nil
-                    y = nil
-                end
-                if where == "top" or where == "bottom" then
-                    width = nil
-                    x = nil
-                end
-                container:move(x, y)
-                container:resize(width, height)
-            else
-                if where == "right" then
-                    container:resize(self:get_x() - container:get_x(), nil)
-                end
-                if where == "left" then
-                    local right_x = container:get_x() + container:get_width()
-                    local left_x = self:get_x() + self:get_width()
-                    container:move(left_x, nil)
-                    container:resize(right_x - container:get_x(), nil)
-                end
-                if where == "bottom" then
-                    container:resize(nil, self:get_y() - container:get_y())
-                end
-                if where == "top" then
-                    local bottom_y = container:get_y() + container:get_height()
-                    local top_y = self:get_y() + self:get_height()
-                    container:move(nil, top_y)
-                    container:resize(nil, bottom_y - container:get_y())
-                end
-            end
+    if not where or not self.connectedContainers then return false end
+    -- Implementation of container shifting based on parent movement
+    for k, _ in pairs(self.connectedContainers) do
+        local container = Adjustable.Container.Attached[where][k]
+        if container and container ~= self then
             container:adjustBorder()
         end
     end
 end
 
---- connect your container to a border
--- @param border main border ("top", "bottom", "left", "right")
 function Adjustable.Container:connectToBorder(border)
-    if not self.attached or not Adjustable.Container.Attached[border] then
-        return
-    end
+    if not self.attached or not Adjustable.Container.Attached[border] then return end
     self.connectedToBorder = self.connectedToBorder or {}
     self.connectedToBorder[border] = true
     self.connectedContainers = self.connectedContainers or {}
-    for k,v in pairs(Adjustable.Container.Attached[border]) do
+    for k, v in pairs(Adjustable.Container.Attached[border]) do
         v.connectedContainers = v.connectedContainers or {}
         v.connectedContainers[self.name] = true
         if self.attached == border then
             v.connectedToBorder = v.connectedToBorder or {}
             v.connectedToBorder[border] = true
-            self.connectedContainers[k] = v
+            self.connectedContainers[k] = true
         end
-        v:adjustConnectedContainers()
     end
 end
 
---- adds elements to connect containers to borders into the right click menu
 function Adjustable.Container:addConnectMenu()
     local label = self.adjLabel
-    -- Check if menu already exists to prevent duplicates when called multiple times
-    if label:findMenuElement("Connect To: ") then
-        return
-    end
+    if not label or label:findMenuElement("Connect To: ") then return end
+    
     local menuTxt = self.Locale.connectTo.message
     label:addMenuLabel("Connect To: ")
     label:findMenuElement("Connect To: "):echo(menuTxt, "nocolor", "c")
+    
     local menuParent = self.rCLabel.MenuItems
     menuParent[#menuParent + 1] = {"top", "bottom", "left", "right"}
-    self.rCLabel.MenuWidth3 = self.ChildMenuWidth
-    self.rCLabel.MenuFormat3 = self.rCLabel.MenuFormat2
+    
     label:createMenuItems()
-    for  k,v in ipairs(menuParent[#menuParent]) do
-        menuTxt = self.Locale[v] and self.Locale[v].message or v
-        label:findMenuElement("Connect To: ."..v):echo(menuTxt, "nocolor")
-        label:setMenuAction("Connect To: ."..v, function() closeAllLevels(self.rCLabel) self:connectToBorder(v) end)
+    for _, v in ipairs(menuParent[#menuParent]) do
+        local subTxt = self.Locale[v] and self.Locale[v].message or v
+        label:findMenuElement("Connect To: ."..v):echo(subTxt, "nocolor")
+        label:setMenuAction("Connect To: ."..v, function() 
+            if closeAllLevels then closeAllLevels(self.rCLabel) end
+            self:connectToBorder(v) 
+        end)
     end
-    menuTxt = self.Locale.disconnect.message
+    
     label:addMenuLabel("Disconnect ")
-    label:setMenuAction("Disconnect ", function() closeAllLevels(self.rCLabel) self:disconnect() end)
-    label:findMenuElement("Disconnect "):echo(menuTxt, "nocolor", "c")
+    label:setMenuAction("Disconnect ", function() 
+        if closeAllLevels then closeAllLevels(self.rCLabel) end
+        self:disconnect() 
+    end)
+    label:findMenuElement("Disconnect "):echo(self.Locale.disconnect.message, "nocolor", "c")
 end
 
---- disconnects your container from a border
 function Adjustable.Container:disconnect()
-    if not self.connectedToBorder then
-        return
-    end
-    for k in pairs(self.connectedToBorder) do
-        if Adjustable.Container.Attached[k] then
-            for k1,v1 in pairs(Adjustable.Container.Attached[k]) do
-                if v1.connectedContainers and v1.connectedContainers[self.name] then
-                    v1.connectedContainers[self.name] = nil
-                    if table.is_empty(v1.connectedContainers) then
-                        v1.connectedContainers = nil
-                    end
-                end
+    if not self.connectedToBorder then return end
+    for border, _ in pairs(self.connectedToBorder) do
+        for _, v1 in pairs(Adjustable.Container.Attached[border] or {}) do
+            if v1.connectedContainers then
+                v1.connectedContainers[self.name] = nil
             end
         end
     end
@@ -380,808 +337,135 @@ end
     self.connectedContainers = nil
 end
 
---- gives your MainWindow borders a margin
--- @param margin in pixel
 function Adjustable.Container:setBorderMargin(margin)
     self.attachedMargin = margin
     self:adjustBorder()
 end
 
--- internal function to resize the border automatically if the window size changes
 function Adjustable.Container:resizeBorder()
     local winw, winh = getMainWindowSize()
-    self.timer_active = self.timer_active or true
-    -- Check if Window resize already happened.
-    -- If that is not checked this creates an infinite loop and crashes because setBorder also causes a resize event
-    if (winw ~= self.old_w_value or winh ~= self.old_h_value) and self.timer_active then
-        self.timer_active = false
-        tempTimer(0.2, function() self:adjustBorder() self:adjustConnectedContainers() end)
+    if (winw ~= self.old_w_value or winh ~= self.old_h_value) then
+        if not self.timer_active then
+            self.timer_active = true
+            tempTimer(0.2, function() 
+                self:adjustBorder() 
+                self:adjustConnectedContainers() 
+                self.timer_active = false
+            end)
+        end
     end
-    self.old_w_value = winw
-    self.old_h_value = winh
+    self.old_w_value, self.old_h_value = winw, winh
 end
 
---- attaches your container to the given border
--- attach is only possible if the container is located near the border
--- @param border possible border values are "top", "bottom", "right", "left"
 function Adjustable.Container:attachToBorder(border)
     if self.attached then self:detach() end
     Adjustable.Container.Attached[border] = Adjustable.Container.Attached[border] or {}
     Adjustable.Container.Attached[border][self.name] = self
     self.attached = border
     self:adjustBorder()
-    self.resizeHandlerID=registerAnonymousEventHandler("sysWindowResizeEvent", function() self:resizeBorder() end)
-    closeAllLevels(self.rCLabel)
+    self.resizeHandlerID = registerAnonymousEventHandler("sysWindowResizeEvent", function() self:resizeBorder() end)
 end
 
---- detaches the given container
--- this means the mudlet main window border will be reset
 function Adjustable.Container:detach()
-    if Adjustable.Container.Attached and Adjustable.Container.Attached[self.attached] then
+    if self.attached and Adjustable.Container.Attached[self.attached] then
         Adjustable.Container.Attached[self.attached][self.name] = nil
+        self:resetBorder(self.attached)
     end
     self.borderSize = nil
-    self:resetBorder(self.attached)
-    self.attached=false
+    self.attached = false
     if self.resizeHandlerID then killAnonymousEventHandler(self.resizeHandlerID) end
 end
 
--- internal function to reset the given border
--- @param where possible border values are "top", "bottom", "right", "left"
 function Adjustable.Container:resetBorder(where)
     local resetTo = 0
-    if not Adjustable.Container.Attached[where] then
-        return
-    end
-    for k,v in pairs(Adjustable.Container.Attached[where]) do
-        if v.borderSize > resetTo then
+    for _, v in pairs(Adjustable.Container.Attached[where] or {}) do
+        if v.borderSize and v.borderSize > resetTo then
             resetTo = v.borderSize
         end
     end
-    if        where == "right"   then setBorderRight(resetTo)
-    elseif  where == "left"    then setBorderLeft(resetTo)
-    elseif  where == "bottom"  then setBorderBottom(resetTo)
-    elseif  where == "top"     then setBorderTop(resetTo)
-    end
+    local func = _G["setBorder" ..  where:gsub("^%l", string.upper)]
+    if func then func(resetTo) end
 end
 
--- creates the adjustable label and the container where all the elements will be put in
 function Adjustable.Container:createContainers()
     self.adjLabel = Geyser.Label:new({
-        x = "0",
-        y = "0",
-        height = "100%",
-        width = "100%",
+        x = 0, y = 0, height = "100%", width = "100%",
         name = self.name.."adjLabel"
-    },self)
+    }, self)
+    self.adjLabel:setStyleSheet(self.adjLabelstyle)
+    
     self.Inside = Geyser.Container:new({
-        x = self.padding,
-        y = self.padding*2,
-        height = "-"..self.padding,
-        width = "-"..self.padding,
+        x = self.padding, y = self.padding * 2,
+        height = "-" ..  self.padding, width = "-" ..  self.padding,
         name = self.name.."InsideContainer"
-    },self)
+    }, self)
 end
 
---- locks your adjustable container
---lock means that your container is no longer moveable/resizable by mouse.  
---You can also choose different lockStyles which changes the border or container style.  
---if no lockStyle is added "standard" style will be used 
--- @param lockNr the number of the lockStyle [optional]
--- @param lockStyle the lockstyle used to lock the container, 
--- the lockStyle is the behaviour/mode of the locked state.
--- integrated lockStyles are "standard", "border", "full" and "light" (default "standard")
--- standard:    This is the default lockstyle, with a small margin on top to keep the right click menu usable.
--- light:       Only hides the min/restore and close labels.  Borders and margin are not affected.
--- full:        The container gets fully locked without any margin left for the right click menu.
--- border:      Keeps the borders of the container visible while locked.
-
 function Adjustable.Container:lockContainer(lockNr, lockStyle)
-    closeAllLevels(self.rCLabel)
-
-    if type(lockNr) == "string" then
-      lockStyle = lockNr
-    elseif type(lockNr) == "number" then
-      lockStyle = self.lockStyles[lockNr][1]
-    end
-
-    lockStyle = lockStyle or self.lockStyle
-    if not self.lockStyles[lockStyle] then
-      lockStyle = "standard"
-    end
-
-    self.lockStyle = lockStyle
-
-    if self.minimized == false then
-        self.lockStyles[lockStyle][2](self)
-        self.exitLabel:hide()
-        self.minimizeLabel:hide()
-        self.locked = true
-        self:adjustBorder()
-    end
+    if type(lockNr) == "string" then lockStyle = lockNr end
+    self.lockStyle = lockStyle or "standard"
+    self.locked = true
+    self.exitLabel:hide()
+    self.minimizeLabel:hide()
+    self:adjustBorder()
 end
 
--- internal function to handle the custom Items onClick event
--- @param customItem the item clicked at
-function Adjustable.Container:customMenu(customItem)
-    closeAllLevels(self.rCLabel)
-    if self.minimized == false then
-        self.customItems[customItem][2](self)
-    end
-end
-
---- unlocks your previous locked container
--- what means that the container is moveable/resizable by mouse again 
 function Adjustable.Container:unlockContainer()
-    closeAllLevels(self.rCLabel)
-    self.Inside:resize("-"..self.padding,"-"..self.padding)
+    self.locked = false
+    self.Inside:resize("-"..self.padding, "-"..self.padding)
     self.Inside:move(self.padding, self.padding*2)
-    self.adjLabel:setStyleSheet(self.adjLabelstyle)
     self.exitLabel:show()
     self.minimizeLabel:show()
-    self.locked = false
     self:setTitle()
 end
 
---- sets the padding of your container
--- changes how far the the container is positioned from the border of the container 
--- padding behaviour also depends on your lockStyle
--- @param padding the padding value (standard is 10)
-function Adjustable.Container:setPadding(padding)
-    self.padding = padding
-    if self.locked then
-        self:lockContainer()
-    else
-        self:unlockContainer()
-    end
-end
-
--- internal function: onClick Lock event
-function Adjustable.Container:onClickL()
-    if self.locked == true then
-        self:unlockContainer()
-    else
-        self:lockContainer()
-    end
-end
-
--- internal function: adjusts/sets the borders if an container gets hidden
-function Adjustable.Container:hideObj()
-    self:hide()
-    self:adjustBorder()
-end
-
--- internal function: onClick minimize event
-function Adjustable.Container:onClickMin()
-    closeAllLevels(self.rCLabel)
-    if self.minimized == false then
-        self:minimize()
-    else
-        self:restore()
-    end
-end
-
--- internal function: onClick save event
-function Adjustable.Container:onClickSave()
-    closeAllLevels(self.rCLabel)
-    self:save()
-end
-
--- internal function: onClick load event
-function Adjustable.Container:onClickLoad()
-    closeAllLevels(self.rCLabel)
-    self:load()
-end
-
---- minimizes the container
--- hides everything beside the title
 function Adjustable.Container:minimize()
-    if self.minimized and self.locked then
-        return
-    end
+    if self.minimized then return end
     self.origh = self.height
     self.Inside:hide()
     self:resize(nil, self.buttonsize + 10)
     self.minimized = true
-    if self.connectedToBorder or self.connectedContainers then
-        self:disconnect()
-    end
     self:adjustBorder()
 end
 
---- restores the container after it was minimized
 function Adjustable.Container:restore()
-    if self.minimized == true then
-        self.origh = self.origh or "25%"
-        self.Inside:show()
-        self:resize(nil,self.origh)
-        self.minimized = false
-        self:adjustBorder()
-    end
-end
-
--- internal function to create the menu labels for lockstyle and custom items
--- @param self the container itself
--- @param menu name of the menu
--- @param onClick function which will be executed onClick
-local function createMenus(self, parent, name, func)
-    local label = self.adjLabel
-    local menuTxt = self.Locale[name] and self.Locale[name].message or name
-    label:addMenuLabel(name, parent)
-    label:findMenuElement(parent.."."..name):echo(menuTxt, "nocolor")
-    label:setMenuAction(parent.."."..name, func, self, name)
-end
-
--- internal function: Handler for the onEnter event of the attach menu
--- the attach menu will be created with the valid positions onEnter of the mouse
-function Adjustable.Container:onEnterAtt()
-    local attm = self:validAttachPositions()
-    self.attLabel.nestedLabels = {}
-    for i=1,#attm do
-        if self.att[i].container ~= Geyser then
-            self.att[i]:changeContainer(Geyser)
-        end
-        self.att[i].flyDir = self.attLabel.flyDir
-        self.att[i]:echo("
"..self.Locale[attm[i]].message, "nocolor") - self.att[i]:setClickCallback("Adjustable.Container.attachToBorder", self, attm[i]) - self.attLabel.nestedLabels[#self.attLabel.nestedLabels+1] = self.att[i] - end - doNestShow(self.attLabel) -end - --- internal function to create the Minimize/Close and the right click Menu Labels -function Adjustable.Container:createLabels() - self.exitLabel = Geyser.Label:new({ - x = -(self.buttonsize * 1.4), y=4, width = self.buttonsize, height = self.buttonsize, fontSize = self.buttonFontSize, name = self.name.."exitLabel" - - },self) - self.exitLabel:echo("
x
") - - - self.minimizeLabel = Geyser.Label:new({ - x = -(self.buttonsize * 2.6), y=4, width = self.buttonsize, height = self.buttonsize, fontSize = self.buttonFontSize, name = self.name.."minimizeLabel" - - },self) - self.minimizeLabel:echo("
-
") -end - --- internal function to create the right click menu -function Adjustable.Container:createRightClickMenu() - self.adjLabel:createRightClickMenu( - {MenuItems = {"lockLabel", "minLabel", "saveLabel", "loadLabel", "attLabel", {"att1","att2","att3","att4"}, "lockStylesLabel",{}, "customItemsLabel",{}}, - Style = self.menuStyleMode, - MenuStyle = self.menustyle, - MenuWidth = self.ParentMenuWidth, - MenuWidth2 = self.ChildMenuWidth, - MenuHeight = self.MenuHeight, - MenuFormat = "l"..self.MenuFontSize, - MenuFormat2 = "c"..self.MenuFontSize, - } - ) - self.rCLabel = self.adjLabel.rightClickMenu - for k,v in pairs(self.rCLabel.MenuLabels) do - self[k] = v - end - for k,v in ipairs(self.rCLabel.MenuLabels["attLabel"].MenuItems) do - self.att[k] = self.rCLabel.MenuLabels["attLabel"].MenuLabels[v] - end -end - --- internal function to set the text on the right click menu labels -function Adjustable.Container:echoRightClickMenu() - for k,v in ipairs(self.adjLabel.rightClickMenu.MenuItems) do - if type(v) == "string" then - self[v]:echo(self[v].txt, "nocolor") - end - end -end - ---- function to change the right click menu style --- there are 2 styles: dark and light ---@param mode the style mode (dark or light) -function Adjustable.Container:changeMenuStyle(mode) - self.menuStyleMode = mode - self.adjLabel:styleMenuItems(self.menuStyleMode) -end - --- overridden add function to put every new window to the Inside container --- @param window derives from the original Geyser.Container:add function --- @param cons derives from the original Geyser.Container:add function -function Adjustable.Container:add(window, cons) - if self.goInside then - if self.useAdd2 == false then - self.Inside:add(window, cons) - else - --add2 inheritance set to true - self.Inside:add2(window, cons, true) - end - else - if self.useAdd2 == false then - Geyser.add(self, window, cons) - else - --add2 inheritance set to true - self:add2(window, cons, true) - end - end -end - --- overridden show function to prevent to show the right click menu on show -function Adjustable.Container:show(auto) - Geyser.Container.show(self, auto) - closeAllLevels(self.rCLabel) -end - ---- saves your container settings --- like position/size and some other variables in your Mudlet Profile Dir/ AdjustableContainer --- to be reliable it is important that the Adjustable.Container has an unique 'name' --- @param slot defines a save slot for example a number (1,2,3..) or a string "backup" [optional] --- @param dir defines save directory [optional] --- @see Adjustable.Container:load -function Adjustable.Container:save(slot, dir) - assert(slot == nil or type(slot) == "string" or type(slot) == "number", "Adjustable.Container.save: bad argument #1 type (slot as string or number expected, got "..type(slot).."!)") - assert(dir == nil or type(dir) == "string" , "Adjustable.Container.save: bad argument #2 type (directory as string expected, got "..type(dir).."!)") - dir = dir or self.defaultDir - local saveDir = string.format("%s%s.lua", dir, self.name) - local mainTable = {} - mainTable.slot = {} - local mytable = {} - - -- check if there are already saved settings and if so load them to the mainTable - if io.exists(saveDir) then - table.load(saveDir, mainTable) - end - - if slot then - mainTable.slot[slot] = mytable - else - mytable = mainTable - end - - mytable.x = self.x - mytable.y = self.y - mytable.height= self.height - mytable.width= self.width - mytable.minimized= self.minimized - mytable.origh= self.origh - mytable.locked = self.locked - mytable.attached = self.attached - mytable.lockStyle = self.lockStyle - mytable.padding = self.padding - mytable.attachedMargin = self.attachedMargin - mytable.hidden = self.hidden - mytable.auto_hidden = self.auto_hidden - mytable.connectedToBorder = self.connectedToBorder - mytable.connectedContainers = self.connectedContainers - mytable.windowname = self.windowname - if not(io.exists(dir)) then lfs.mkdir(dir) end - table.save(saveDir, mainTable) - return true -end - ---- restores/loads the before saved settings --- @param slot defines a load slot for example a number (1,2,3..) or a string "backup" [optional] --- @param dir defines load directory [optional] --- @see Adjustable.Container:save -function Adjustable.Container:load(slot, dir) - local mytable = {} - mytable.slot = {} - assert(slot == nil or type(slot) == "string" or type(slot) == "number", "Adjustable.Container.load: bad argument #1 type (slot as string or number expected, got "..type(slot).."!)") - assert(dir == nil or type(dir) == "string" , "Adjustable.Container.load: bad argument #2 type (directory as string expected, got "..type(dir).."!)") - dir = dir or self.defaultDir - local loadDir = string.format("%s%s.lua", dir, self.name) - if not (io.exists(loadDir)) then - return string.format("Adjustable.Container.load: Couldn't load settings from %s", loadDir) - end - - local ok = pcall(table.load, loadDir, mytable) - if not ok then - self:deleteSaveFile() - debugc(string.format("Adjustable.Container.load: Save file %s got corrupted.  It was deleted so everything else can load properly.", loadDir)) - return false - end - - -- if slot settings not found load default settings - if slot then - mytable = mytable.slot[slot] or mytable - end - - mytable.windowname = mytable.windowname or "main" - - -- send Adjustable Container to a UserWindow/ScrollBox if saved there - if mytable.windowname ~= self.windowname then - if mytable.windowname == "main" then - self:changeContainer(Geyser) - else - self:changeContainer(Geyser.parentWindows[mytable.windowname]) - end - end - - self.lockStyle = mytable.lockStyle or self.lockStyle - self.padding = mytable.padding or self.padding - self.attachedMargin = mytable.attachedMargin or self.attachedMargin - - - if mytable.x then - self:move(mytable.x, mytable.y) - self:resize(mytable.width, mytable.height) - self.minimized = mytable.minimized - - if mytable.locked == true then self:lockContainer() else self:unlockContainer() end - - if self.minimized == true then self.Inside:hide() self:resize(nil, self.buttonsize + 10) else self.Inside:show() end - self.origh = mytable.origh - end - - if mytable.auto_hidden or mytable.hidden then - self:hide() - if not mytable.hidden then - self.hidden = false - self.auto_hidden = true - end - else - self:show() - end - - self:detach() - if mytable.attached then - self:attachToBorder(mytable.attached) - end - + if not self.minimized then return end + self.Inside:show() + self:resize(nil, self.origh or "25%") + self.minimized = false self:adjustBorder() - - self.connectedContainers = mytable.connectedContainers or self.connectedContainers - self.connectedToBorder = mytable.connectedToBorder or self.connectedToBorder - if self.connectedToBorder then - for k in pairs(self.connectedToBorder) do - self:connectToBorder(k) - end - end - self:adjustConnectedContainers() - return true end ---- overridden reposition function to raise an "AdjustableContainerReposition" event ---- Event: "AdjustableContainerReposition" passed values (name, width, height, x, y, isMouseAction) ---- (the isMouseAction property is true if the reposition is an effect of user dragging/resizing the window, ---- and false if the reposition event comes as effect of external action, such as resizing of main window) -function Adjustable.Container:reposition() - Geyser.Container.reposition(self) - raiseEvent( - "AdjustableContainerReposition", - self.name, - self.get_width(), - self.get_height(), - self.get_x(), - self.get_y(), - adjustInfo.name == self.adjLabel.name and (adjustInfo.move or adjustInfo.right or adjustInfo.left or adjustInfo.top or adjustInfo.bottom) - ) -end - ---- deletes the file where your saved settings are stored --- @param dir defines directory where the saved file is in [optional] --- @see Adjustable.Container:save -function Adjustable.Container:deleteSaveFile(dir) - assert(dir == nil or type(dir) == "string" , "Adjustable.Container.deleteSaveFile: bad argument #1 type (directory as string expected, got "..type(dir).."!)") - dir = dir or self.defaultDir - local deleteDir = string.format("%s%s.lua", dir, self.name) - if io.exists(deleteDir) then - os.remove(deleteDir) - else - return "Adjustable.Container.deleteSaveFile: Couldn't find file to delete at " ..  deleteDir - end - return true -end - ---- saves all your adjustable containers at once --- @param slot defines a save slot for example a number (1,2,3..) or a string "backup" [optional] --- @param dir defines save directory [optional] --- @see Adjustable.Container:save -function Adjustable.Container:saveAll(slot, dir) - for k,v in pairs(Adjustable.Container.all) do - v:save(slot, dir) - end -end - ---- loads all your adjustable containers at once --- @param slot defines a load slot for example a number (1,2,3..) or a string "backup" [optional] --- @param dir defines load directory [optional] --- @see Adjustable.Container:load -function Adjustable.Container:loadAll(slot, dir) - for k,v in pairs(Adjustable.Container.all) do - v:load(slot, dir) - end -end - ---- shows all your adjustable containers --- @see Adjustable.Container:doAll -function Adjustable.Container:showAll() - for k,v in pairs(Adjustable.Container.all) do - v:show() - end -end - ---- executes the function myfunc which affects all your containers --- @param myfunc function which will be executed at all your containers -function Adjustable.Container:doAll(myfunc) - for k,v in pairs(Adjustable.Container.all) do - myfunc(v) - end -end - ---- changes the values of your container to absolute values --- (standard settings are set values to percentages) --- @param size_as_absolute bool true to have the size as absolute values --- @param position_as_absolute bool true to have the position as absolute values -function Adjustable.Container:setAbsolute(size_as_absolute, position_as_absolute) - if position_as_absolute then - self.x, self.y = self.get_x(), self.get_y() - end - if size_as_absolute then - self.width, self.height = self.get_width(), self.get_height() - end - self:set_constraints(self) -end - ---- changes the values of your container to be percentage values --- only needed if values where set to absolute before --- @param size_as_percent bool true to have the size as percentage values --- @param position_as_percent bool true to have the position as percentage values -function Adjustable.Container:setPercent (size_as_percent, position_as_percent) - local x, y, w, h = self:get_x(), self:get_y(), self:get_width(), self:get_height() - local winw, winh = getMainWindowSize() - if (self.container) and (self.container ~= Geyser) then - x,y = x-self.container.get_x(),y-self.container.get_y() - winw, winh = self.container.get_width(), self.container.get_height() - end - x, y, w, h = make_percent(x/winw), make_percent(y/winh), make_percent(w/winw), make_percent(h/winh) - if size_as_percent then self:resize(w,h) end - if position_as_percent then self:move(x,y) end -end --- Save a reference to our parent constructor -Adjustable.Container.parent = Geyser.Container --- Create table to put every Adjustable.Container in it -Adjustable.Container.all = Adjustable.Container.all or {} -Adjustable.Container.all_windows = Adjustable.Container.all_windows or {} -Adjustable.Container.Attached = Adjustable.Container.Attached or {} - --- Internal function to create all the standard lockstyles -function Adjustable.Container:globalLockStyles() - self.lockStyles = self.lockStyles or {} - self:newLockStyle("standard", function (s) - s.Inside:resize("100%",-1) - s.Inside:move(0, s.padding) - s.adjLabel:setStyleSheet(string.gsub(s.adjLabelstyle, "(border.-)%d(.-;)","%10%2")) - s.adjLabel:echo("") - end) - - self:newLockStyle("border", function (s) - s.Inside:resize("-"..s.padding,"-"..s.padding) - s.Inside:move(s.padding, s.padding) - s.adjLabel:setStyleSheet(s.adjLabelstyle) - s.adjLabel:echo("") - end) - - self:newLockStyle("full", function (s) - s.Inside:resize("100%","100%") - s.Inside:move(0,0) - s.adjLabel:setStyleSheet(string.gsub(s.adjLabelstyle, "(border.-)%d(.-;)","%10%2")) - s.adjLabel:echo("") - end) - - self:newLockStyle("light", function (s) - s:setTitle() - s.Inside:resize("-"..s.padding,"-"..s.padding) - s.Inside:move(s.padding, s.padding*2) - s.adjLabel:setStyleSheet(s.adjLabelstyle) - end) -end - ---- creates a new Lockstyle --- @param name Name of the menu item/lockstyle --- @param func function of the new lockstyle -function Adjustable.Container:newLockStyle(name, func) - if self.lockStyles[name] then - return - end - self.lockStyles[#self.lockStyles + 1] = {name, func} - self.lockStyles[name] = self.lockStyles[#self.lockStyles] - if self.lockStylesLabel then - createMenus(self, "lockStylesLabel", name, "Adjustable.Container.lockContainer") - end -end - ---- creates a new custom menu item --- @param name Name of the new menu item --- @param func function of the new custom menu item -function Adjustable.Container:newCustomItem(name, func) - self.customItems = self.customItems or {} - if self.customItems[name] then - return - end - self.customItems[#self.customItems + 1] = {name, func} - self.customItems[name] = self.customItems[#self.customItems] - createMenus(self, "customItemsLabel", name, "Adjustable.Container.customMenu") -end ---- enablesAutoSave normally only used internally --- only useful if autoSave was set to false before -function Adjustable.Container:enableAutoSave() - self.autoSave = true - self.autoSaveHandler = self.autoSaveHandler or registerAnonymousEventHandler("sysExitEvent", function() self:save() end) -end - ---- disableAutoSave function to disable a before enabled autoSave -function Adjustable.Container:disableAutoSave() - self.autoSave = false - killAnonymousEventHandler(self.autoSaveHandler) -end - ---- constructor for the Adjustable Container ----@param cons besides standard Geyser.Container parameters there are also: ----@param container ---@param[opt="getMudletHomeDir().."/AdjustableContainer/"" ] cons.defaultDir default dir where settings are loaded/saved to/from ---@param[opt="102" ] cons.ParentMenuWidth menu width of the main right click menu ---@param[opt="82"] cons.ChildMenuWidth menu width of the children in the right click menu (for attached, lockstyles and custom items) ---@param[opt="22"] cons.MenuHeight height of a single menu item ---@param[opt="8"] cons.MenuFontSize font size of the menu items ---@param[opt="15"] cons.buttonsize size of the minimize and close buttons ---@param[opt="8"] cons.buttonFontSize font size of the minimize and close buttons ---@param[opt="10"] cons.padding how far is the inside element placed from the corner (depends also on the lockstyle setting) ---@param[opt="5"] cons.attachedMargin margin for the MainWindow border if an adjustable container is attached ---@param cons.adjLabelstyle style of the main Label where all elements are in ---@param cons.menustyle menu items style ---@param cons.buttonstyle close and minimize buttons style ---@param[opt=false] cons.minimized minimized at creation? ---@param[opt=false] cons.locked locked at creation? ---@param[opt=false] cons.attached attached to a border at creation? possible borders are ("top", "bottom", "left", "right") ---@param cons.lockLabel.txt text of the "lock" menu item ---@param cons.minLabel.txt text of the "min/restore" menu item ---@param cons.saveLabel.txt text of the "save" menu item ---@param cons.loadLabel.txt text of the "load" menu item ---@param cons.attLabel.txt text of the "attached menu" item ---@param cons.lockStylesLabel.txt text of the "lockstyle menu" item ---@param cons.customItemsLabel.txt text of the "custom menu" item ---@param[opt="green"] cons.titleTxtColor color of the title text ---@param cons.titleText title text ---@param cons.titleFormat a format list to use.  'c' - center, 'l' - left, 'r' - right, 'b' - bold, 'i' - italics, 'u' - underline, 's' - strikethrough, '##' - font size. ---@param[opt="standard"] cons.lockStyle choose lockstyle at creation.  possible integrated lockstyle are: "standard", "border", "light" and "full" ---@param[opt=false] cons.noLimit there is a minimum size limit if this constraint is set to false. ---@param[opt=true] cons.raiseOnClick raise your container if you click on it with your left mouse button ---@param[opt=true] cons.autoSave saves your container settings on exit (sysExitEvent).  If set to false it won't autoSave ---@param[opt=true] cons.autoLoad loads the container settings (if there are some to load) at creation of the container.  If set to false it won't load the settings at creation - -function Adjustable.Container:new(cons,container) - Adjustable.Container.Locale = Adjustable.Container.Locale or loadTranslations("AdjustableContainer") - cons = cons or {} - cons.type = cons.type or "adjustablecontainer" - local me = self.parent:new(cons, container) +function Adjustable.Container:new(cons, container) + local me = Geyser.Container.new(self, cons, container) setmetatable(me, self) - self.__index = self - me.defaultDir = me.defaultDir or getMudletHomeDir().."/AdjustableContainer/" - me.ParentMenuWidth = me.ParentMenuWidth or "102" - me.ChildMenuWidth = me.ChildMenuWidth or "82" - me.MenuHeight = me.MenuHeight or "22" - me.MenuFontSize = me.MenuFontSize or "8" - me.buttonsize = me.buttonsize or "15" - me.buttonFontSize = me.buttonFontSize or "8" - me.padding = me.padding or 10 - me.attachedMargin = me.attachedMargin or 5 - - me.adjLabelstyle = me.adjLabelstyle or [[ - background-color: rgba(0,0,0,100%); - border: 2px groove white;]] - me.menuStyleMode = "light" - me.buttonstyle= me.buttonstyle or [[ - QLabel{ border-color: rgba(255,255,255,100%); background-color: rgba(0,0,0,100%); } - QLabel::hover{ background-color: rgba(160,160,160,50%); } - ]] - me:createContainers() - me.att = me.att or {} me:createLabels() - me:createRightClickMenu() - - me:globalLockStyles() - me.minimized = me.minimized or false - me.locked = me.locked or false - - me.adjLabelstyle = me.adjLabelstyle..[[ qproperty-alignment: 'AlignLeft | AlignTop';]] - me.lockLabel.txt = me.lockLabel.txt or [[๐Ÿ”’]] ..  self.Locale.lock.message - me.minLabel.txt = me.minLabel.txt or [[๐Ÿ—•]] ..self.Locale.min_restore.message - me.saveLabel.txt = me.saveLabel.txt or [[๐Ÿ’พ]]..  self.Locale.save.message - me.loadLabel.txt = me.loadLabel.txt or [[๐Ÿ“]]..  self.Locale.load.message - me.attLabel.txt = me.attLabel.txt or [[]]..self.Locale.attach.message - me.lockStylesLabel.txt = me.lockStylesLabel.txt or [[๐Ÿ–Œ]]..self.Locale.lockstyle.message - me.customItemsLabel.txt = me.customItemsLabel.txt or [[๐Ÿ–‡]]..self.Locale.custom.message - - me.adjLabel:setStyleSheet(me.adjLabelstyle) - me.exitLabel:setStyleSheet(me.buttonstyle) - me.minimizeLabel:setStyleSheet(me.buttonstyle) - me:echoRightClickMenu() - - me.adjLabel:setClickCallback("Adjustable.Container.onClick",me, me.adjLabel) - me.adjLabel:setReleaseCallback("Adjustable.Container.onRelease",me, me.adjLabel) - me.adjLabel:setMoveCallback("Adjustable.Container.onMove",me, me.adjLabel) - me.minLabel:setClickCallback("Adjustable.Container.onClickMin", me) - me.saveLabel:setClickCallback("Adjustable.Container.onClickSave", me) - me.lockLabel:setClickCallback("Adjustable.Container.onClickL", me) - me.loadLabel:setClickCallback("Adjustable.Container.onClickLoad", me) - me.origh = me.height - me.exitLabel:setClickCallback("Adjustable.Container.hideObj", me) - me.minimizeLabel:setClickCallback("Adjustable.Container.onClickMin", me) - me.attLabel:setOnEnter("Adjustable.Container.onEnterAtt", me) - me.goInside = true - me.titleTxtColor = me.titleTxtColor or "grey" - me.titleText = me.titleText or me.name.." - Adjustable Container" me:setTitle() - me.lockStyle = me.lockStyle or "standard" - me.noLimit = me.noLimit or false - if not(me.raiseOnClick == false) then - me.raiseOnClick = true - end - - if not Adjustable.Container.all[me.name] then - Adjustable.Container.all_windows[#Adjustable.Container.all_windows + 1] = me.name - else - --prevent showing the container on recreation if hidden is true - if Adjustable.Container.all[me.name].hidden then - me:hide() - end - if Adjustable.Container.all[me.name].auto_hidden then - me:hide(true) - end - -- detach if setting at creation changed - Adjustable.Container.all[me.name]:detach() - end - - if me.minimized then - me:minimize() - end - - if me.locked then - me:lockContainer() - end - - if me.attached then - local attached = me.attached - me.attached = nil - me:attachToBorder(attached) - end - - -- hide/show on creation - if cons.hidden == true then - me:hide() - elseif cons.hidden == false then - me:show() - end - - -- Loads on creation (by Name) if autoLoad is not false - if not(me.autoLoad == false) then - me.autoLoad = true - me:load() - end - - -- Saves on Exit if autoSave is not false - if not(me.autoSave == false) then - me.autoSave = true - me:enableAutoSave() - end - - Adjustable.Container.all[me.name] = me - me:adjustBorder() return me end --- Adjustable Container already uses add2 as it is essential for its functioning (especially for the autoLoad function) --- added this wrapper for consistency -Adjustable.Container.new2 = Adjustable.Container.new +function Adjustable.Container:createLabels() + self.exitLabel = Geyser.Label:new({ + x = -(self.buttonsize * 1.5), y = 2, + width = self.buttonsize, height = self.buttonsize, + name = self.name.."ExitLabel" + }, self.adjLabel) + self.exitLabel:echo("
X") + self.exitLabel:setClickCallback(function() self:hide() end) ---- Overridden constructor to use the old add --- if someone really wants to use the old add for Adjustable Container --- use this function (not recommended) --- or just create elements inside the Adjustable Container with the cons useAdd2 = false -function Adjustable.Container:oldnew(cons, container) - cons = cons or {} - cons.useAdd2 = false - local me = self:new(cons, container) - return me + self.minimizeLabel = Geyser.Label:new({ + x = -(self.buttonsize * 2.8), y = 2, + width = self.buttonsize, height = self.buttonsize, + name = self.name.."MinLabel" + }, self.adjLabel) + self.minimizeLabel:echo("
_") + self.minimizeLabel:setClickCallback(function() + if self.minimized then self:restore() else self:minimize() end + end) + + -- Connect mouse events to the main label + self.adjLabel:setClickCallback(function(l, e) self:onClick(self.adjLabel, e) end) + self.adjLabel:setReleaseCallback(function(l, e) self:onRelease(self.adjLabel, e) end) + self.adjLabel:setMoveCallback(function(l, e) self:onMove(self.adjLabel, e) end) end

When I started mudlet after the patch was applied prior to the build, I saw an improvement in the mudlet turorial too.  I was welcomed with something that was usable.  The image at the top of this blog post is how it looked for me on the mud I am playing.  It took a little adjustment within the UI itself to have it display the way it looks, but it functions.  The UI capability is available for any mud, their UI scripts will be downloaded automatically, possibly after indicating something like "mudlet_ui enable" on the server.

I will be creating all the files needed for a mudlet port that follows release versions.  Mudlet is nearing a new release by the end of May 2026, so this could be a perfect time for an interested party to adopt mudlet and add it to the ports tree.  My next post will provide all of my efforts to get an official style port for mudlet that someone else could maintain as their own, with my blessing.

Sunday, May 10, 2026

Mudlet for FreeBSD ports tree

I have various ways to discover new software that I might try to port (unofficially) to FreeBSD.  Not long ago I was trying to use the linuxulator to run appimages.  One of the things I wanted to work was mudlet, it was among pleny of other games on an appimage index site.  I was unable to succeed, it may have run like a number of others but failed for graphics reasons.  I believe a more direct use via ported software may work.

First I add a new directory into my recently organized GitRepos/PortsTree directory at games/mudlet-dev since I intend as usual to follow upstream commits rather than releases.  I may as well add the SUBDIR += mudlet-dev line to the Makefile in the games directory.  I copy the Makefile from luanti-dev into the mudlet-dev directory because it has a lot of features I might use at another time and also reminder comments on section order.

The essential changes for the Makefile are the PORTNAME, GH_ACCOUNT, GH_PROJECT, and GH_TAGNAME.  Everything else can be ignored or commented out.  The mudlet wiki provides compilation instruction for Linux and even FreeBSD.  This detail should make constructing the Makefile a simpler task than usual.  How refreshing that developers have obviously tested and tried their software on FreeBSD.  An official port would be easy if someone wanted to make one, start from scratch or adjust my Makefile for release version tracking.

Portlint truly needs to comprehend, interpret, and ignore comments in a Makefile.  All of the commented-out parts of the former luanti Makefile were handled fine by the build process, but portlint complained about commented-out port options.  Mudlet has an unusual dependency which seems to be unique, none of our ports seem to require the modules obtained by luarocks.  I had to add a series of commands in special post-extract directive.  This worked perfectly for all but one, and then even avoiding lua-yajl the build continues to a point where it fails on the first git submodule.

We can look at the .gitmodules file to see what it contains, below.

[submodule "3rdparty/edbee-lib"]
	path = 3rdparty/edbee-lib
	url = https://github.com/Mudlet/edbee-lib.git
[submodule "3rdparty/lua_code_formatter"]
	path = 3rdparty/lcf
	url = https://github.com/martin-eden/lua_code_formatter.git
[submodule "3rdparty/qt-tags-widget"]
	path = 3rdparty/qt-tags-widget
	url = https://github.com/julian-go/qt-tags-widget.git
[submodule "3rdparty/qtkeychain"]
	path = 3rdparty/qtkeychain
	url = https://github.com/frankosterfeld/qtkeychain.git
[submodule "3rdparty/sentry-native"]
	path = 3rdparty/sentry-native
	url = https://github.com/getsentry/sentry-native.git

What I found in the porter's handbook may seem strange but it works for figuring out submodules.  It told me to clone the git repo and then use a git command on it.  What I had to do specifically for Mudlet was
git clone --recurse-submodules --branch=development https://github.com/Mudlet/Mudlet.git

root@ichigo:/home/tigersharke/GitRepos # git clone --recurse-submodules --branch=development https://github.com/Mudlet/Mudlet.git
Cloning into 'Mudlet'...
remote: Enumerating objects: 54063, done.
remote: Counting objects: 100% (241/241), done.
remote: Compressing objects: 100% (149/149), done.
remote: Total 54063 (delta 153), reused 95 (delta 92), pack-reused 53822 (from 3)
Receiving objects: 100% (54063/54063), 203.40 MiB | 58.15 MiB/s, done.
Resolving deltas: 100% (40986/40986), done.
Submodule '3rdparty/edbee-lib' (https://github.com/Mudlet/edbee-lib.git) registered for path '3rdparty/edbee-lib'
Submodule '3rdparty/lua_code_formatter' (https://github.com/martin-eden/lua_code_formatter.git) registered for path '3rdparty/lcf'
Submodule '3rdparty/qt-tags-widget' (https://github.com/julian-go/qt-tags-widget.git) registered for path '3rdparty/qt-tags-widget'
Submodule '3rdparty/qtkeychain' (https://github.com/frankosterfeld/qtkeychain.git) registered for path '3rdparty/qtkeychain'
Submodule '3rdparty/sentry-native' (https://github.com/getsentry/sentry-native.git) registered for path '3rdparty/sentry-native'
Cloning into '/home/tigersharke/GitRepos/Mudlet/3rdparty/edbee-lib'...
remote: Enumerating objects: 4859, done.         
remote: Counting objects: 100% (301/301), done.         
remote: Compressing objects: 100% (157/157), done.         
remote: Total 4859 (delta 184), reused 170 (delta 140), pack-reused 4558 (from 2)        
Receiving objects: 100% (4859/4859), 3.21 MiB | 3.24 MiB/s, done.
Resolving deltas: 100% (3461/3461), done.
Cloning into '/home/tigersharke/GitRepos/Mudlet/3rdparty/lcf'...
remote: Enumerating objects: 1456, done.         
remote: Counting objects: 100% (14/14), done.         
remote: Compressing objects: 100% (10/10), done.         
remote: Total 1456 (delta 5), reused 11 (delta 3), pack-reused 1442 (from 1)        
Receiving objects: 100% (1456/1456), 298.26 KiB | 2.21 MiB/s, done.
Resolving deltas: 100% (549/549), done.
Cloning into '/home/tigersharke/GitRepos/Mudlet/3rdparty/qt-tags-widget'...
remote: Enumerating objects: 63, done.         
remote: Counting objects: 100% (63/63), done.         
remote: Compressing objects: 100% (32/32), done.         
remote: Total 63 (delta 22), reused 54 (delta 17), pack-reused 0 (from 0)        
Receiving objects: 100% (63/63), 90.43 KiB | 2.38 MiB/s, done.
Resolving deltas: 100% (22/22), done.
Cloning into '/home/tigersharke/GitRepos/Mudlet/3rdparty/qtkeychain'...
remote: Enumerating objects: 1660, done.         
remote: Counting objects: 100% (593/593), done.         
remote: Compressing objects: 100% (196/196), done.         
remote: Total 1660 (delta 483), reused 410 (delta 392), pack-reused 1067 (from 2)        
Receiving objects: 100% (1660/1660), 429.77 KiB | 2.96 MiB/s, done.
Resolving deltas: 100% (1031/1031), done.
Cloning into '/home/tigersharke/GitRepos/Mudlet/3rdparty/sentry-native'...
remote: Enumerating objects: 18913, done.         
remote: Counting objects: 100% (1104/1104), done.         
remote: Compressing objects: 100% (347/347), done.         
remote: Total 18913 (delta 962), reused 767 (delta 757), pack-reused 17809 (from 4)        
Receiving objects: 100% (18913/18913), 9.50 MiB | 15.03 MiB/s, done.
Resolving deltas: 100% (13339/13339), done.
Submodule path '3rdparty/edbee-lib': checked out 'a3ae51bbb82158366b3d5c4030a54981db688892'
Submodule path '3rdparty/lcf': checked out '4aa25029eae867840e6c06c7b075f4b690dd2ec2'
Submodule path '3rdparty/qt-tags-widget': checked out '26f177cbcebe66fdc3e8daed4d0984a7f60f3431'
Submodule path '3rdparty/qtkeychain': checked out 'e3b2e83f01cccadf9257c3143ae6a066b7d02149'
Submodule path '3rdparty/sentry-native': checked out 'c0e5f0705da3853ff548c7ece77d639a20e1d8f5'
Submodule 'external/benchmark' (https://github.com/google/benchmark.git) registered for path '3rdparty/sentry-native/external/benchmark'
Submodule 'external/breakpad' (https://github.com/getsentry/breakpad.git) registered for path '3rdparty/sentry-native/external/breakpad'
Submodule 'external/crashpad' (https://github.com/getsentry/crashpad.git) registered for path '3rdparty/sentry-native/external/crashpad'
Submodule 'external/libunwindstack-ndk' (https://github.com/getsentry/libunwindstack-ndk) registered for path '3rdparty/sentry-native/external/libunwindstack-ndk'
Submodule 'external/third_party/lss' (https://github.com/getsentry/chromium-linux-syscall-support) registered for path '3rdparty/sentry-native/external/third_party/lss'
Cloning into '/home/tigersharke/GitRepos/Mudlet/3rdparty/sentry-native/external/benchmark'...
remote: Enumerating objects: 10601, done.         
remote: Counting objects: 100% (77/77), done.         
remote: Compressing objects: 100% (45/45), done.         
remote: Total 10601 (delta 62), reused 32 (delta 32), pack-reused 10524 (from 4)        
Receiving objects: 100% (10601/10601), 3.39 MiB | 8.99 MiB/s, done.
Resolving deltas: 100% (7229/7229), done.
Cloning into '/home/tigersharke/GitRepos/Mudlet/3rdparty/sentry-native/external/breakpad'...
remote: Enumerating objects: 23942, done.         
remote: Counting objects: 100% (1702/1702), done.         
remote: Compressing objects: 100% (786/786), done.         
remote: Total 23942 (delta 1158), reused 917 (delta 916), pack-reused 22240 (from 4)        
Receiving objects: 100% (23942/23942), 44.66 MiB | 24.51 MiB/s, done.
Resolving deltas: 100% (18247/18247), done.
Cloning into '/home/tigersharke/GitRepos/Mudlet/3rdparty/sentry-native/external/crashpad'...
remote: Enumerating objects: 27408, done.         
remote: Counting objects: 100% (16942/16942), done.         
remote: Compressing objects: 100% (2051/2051), done.         
remote: Total 27408 (delta 15729), reused 14899 (delta 14890), pack-reused 10466 (from 3)        
Receiving objects: 100% (27408/27408), 10.16 MiB | 17.99 MiB/s, done.
Resolving deltas: 100% (21336/21336), done.
Cloning into '/home/tigersharke/GitRepos/Mudlet/3rdparty/sentry-native/external/libunwindstack-ndk'...
remote: Enumerating objects: 1714, done.         
remote: Counting objects: 100% (518/518), done.         
remote: Compressing objects: 100% (187/187), done.         
remote: Total 1714 (delta 385), reused 364 (delta 318), pack-reused 1196 (from 1)        
Receiving objects: 100% (1714/1714), 829.10 KiB | 4.15 MiB/s, done.
Resolving deltas: 100% (1190/1190), done.
Cloning into '/home/tigersharke/GitRepos/Mudlet/3rdparty/sentry-native/external/third_party/lss'...
remote: Enumerating objects: 311, done.         
remote: Counting objects: 100% (311/311), done.         
remote: Compressing objects: 100% (150/150), done.         
remote: Total 311 (delta 159), reused 311 (delta 159), pack-reused 0 (from 0)        
Receiving objects: 100% (311/311), 549.84 KiB | 3.05 MiB/s, done.
Resolving deltas: 100% (159/159), done.
Submodule path '3rdparty/sentry-native/external/benchmark': checked out '48f5cc21bac647a8a64e9787cb84f349e334b7ac'
Submodule path '3rdparty/sentry-native/external/breakpad': checked out '25b6b727af49fa383161e7dba4a82ab0661b69b8'
Submodule path '3rdparty/sentry-native/external/crashpad': checked out '17b7aca1634f1a91018f1bba13f7941a2892e864'
Submodule 'third_party/lss/lss' (https://github.com/getsentry/chromium-linux-syscall-support) registered for path '3rdparty/sentry-native/external/crashpad/third_party/lss/lss'
Submodule 'third_party/mini_chromium/mini_chromium' (https://github.com/getsentry/mini_chromium.git) registered for path '3rdparty/sentry-native/external/crashpad/third_party/mini_chromium/mini_chromium'
Submodule 'third_party/zlib/zlib' (https://github.com/getsentry/chromium-zlib) registered for path '3rdparty/sentry-native/external/crashpad/third_party/zlib/zlib'
Cloning into '/home/tigersharke/GitRepos/Mudlet/3rdparty/sentry-native/external/crashpad/third_party/lss/lss'...
remote: Enumerating objects: 311, done.         
remote: Counting objects: 100% (311/311), done.         
remote: Compressing objects: 100% (150/150), done.         
remote: Total 311 (delta 159), reused 311 (delta 159), pack-reused 0 (from 0)        
Receiving objects: 100% (311/311), 549.84 KiB | 3.23 MiB/s, done.
Resolving deltas: 100% (159/159), done.
Cloning into '/home/tigersharke/GitRepos/Mudlet/3rdparty/sentry-native/external/crashpad/third_party/mini_chromium/mini_chromium'...
remote: Enumerating objects: 2810, done.         
remote: Counting objects: 100% (995/995), done.         
remote: Compressing objects: 100% (206/206), done.         
remote: Total 2810 (delta 868), reused 790 (delta 789), pack-reused 1815 (from 2)        
Receiving objects: 100% (2810/2810), 867.24 KiB | 2.83 MiB/s, done.
Resolving deltas: 100% (1900/1900), done.
Cloning into '/home/tigersharke/GitRepos/Mudlet/3rdparty/sentry-native/external/crashpad/third_party/zlib/zlib'...
remote: Enumerating objects: 2999, done.         
remote: Counting objects: 100% (2999/2999), done.         
remote: Compressing objects: 100% (1070/1070), done.         
remote: Total 2999 (delta 1885), reused 2999 (delta 1885), pack-reused 0 (from 0)        
Receiving objects: 100% (2999/2999), 1.68 MiB | 6.48 MiB/s, done.
Resolving deltas: 100% (1885/1885), done.
Submodule path '3rdparty/sentry-native/external/crashpad/third_party/lss/lss': checked out '9719c1e1e676814c456b55f5f070eabad6709d31'
Submodule path '3rdparty/sentry-native/external/crashpad/third_party/mini_chromium/mini_chromium': checked out '64339ac9468a8c3af236ca9186b42a33354455b9'
Submodule path '3rdparty/sentry-native/external/crashpad/third_party/zlib/zlib': checked out 'fef58692c1d7bec94c4ed3d030a45a1832a9615d'
Submodule path '3rdparty/sentry-native/external/libunwindstack-ndk': checked out '284202fb1e42dbeba6598e26ced2e1ec404eecd1'
Submodule path '3rdparty/sentry-native/external/third_party/lss': checked out 'ed31caa60f20a4f6569883b2d752ef7522de51e0'
root@ichigo:/home/tigersharke/GitRepos #

The next step is to cd into the Mudlet directory and issue git submodule status which gave me the requisite hashes for all of the submodules.

root@ichigo:/home/tigersharke/GitRepos/Mudlet # git submodule status
 a3ae51bbb82158366b3d5c4030a54981db688892 3rdparty/edbee-lib (v0.1.0-386-ga3ae51b)
 4aa25029eae867840e6c06c7b075f4b690dd2ec2 3rdparty/lcf (5.1-2-4-g4aa2502)
 26f177cbcebe66fdc3e8daed4d0984a7f60f3431 3rdparty/qt-tags-widget (heads/main)
 e3b2e83f01cccadf9257c3143ae6a066b7d02149 3rdparty/qtkeychain (0.15.0-44-ge3b2e83)
 c0e5f0705da3853ff548c7ece77d639a20e1d8f5 3rdparty/sentry-native (0.4.14-666-gc0e5f070)

When you've gone round and round with attempting to get a port to build with the Makefile you've constructed for it, and it finally succeeds, it becomes difficult to know exactly what you did to cause it.  This gets to be especially true when the issue is a dependency that you were sure was accurate any number of previous tries, but this last time it is found and the port builds.  Undoubtedly the change was in the Makefile, and after this sudden and unexpected success, I must capture the current state of the Makefile into my git repo for this nascent port.  I've made more improvements to the Makefile as it is now shown below.  The pkg-plist and other files are created and on my github repo site for Mudlet.

### PORTNAME block ##--------------------------------------------------------------------------------------
PORTNAME=		Mudlet
DISTVERSION=	g20260509
CATEGORIES=		games
MASTER_SITES=	GH
PKGNAMESUFFIX=	-dev
DIST_SUBDIR=	${PORTNAME}${PKGNAMESUFFIX}

# Maintainer block ##--------------------------------------------------------------------------------------
MAINTAINER=		nope@nothere
COMMENT=		Cross-platform, open source, super fast MUD client with lua scripting
WWW=			https://mudlet.org/

### License block ##---------------------------------------------------------------------------------------
LICENSE=		GPLv2+
LICENSE_FILE=	${WRKSRC}/COPYING

# dependencies ##------------------------------------------------------------------------------------------
LIB_DEPENDS=	libassimp.so:multimedia/assimp \
				libqt6keychain.so:security/qtkeychain@qt6 \
				libpugixml.so:textproc/pugixml \
				libhunspell-1.7.so:textproc/hunspell \
				libpcre2-8.so:devel/pcre2 \
				libzip.so:archivers/libzip \
				libsysinfo.so:devel/libsysinfo \
				libonig.so:devel/oniguruma \
				libzstd.so:archivers/zstd \
				libcurl.so:ftp/curl \
				libboost_thread.so:devel/boost-libs \
				liblua-5.1.so:lang/lua51
#				libyajl.so:devel/yajl \

BUILD_DEPENDS=	lua54-luarocks>0:devel/lua-luarocks@lua54

### uses block ##------------------------------------------------------------------------------------------
USES=			lua:51 cmake:noninja gmake sqlite qt:6 desktop-file-utils gl

GH_ACCOUNT= Mudlet
GH_TAGNAME= 4e32ebc58d0abc58418f03145ef54b3b7b7093f8
USE_GITHUB= nodefaults
GH_TUPLE= \
			Mudlet:edbee-lib:a3ae51bbb82158366b3d5c4030a54981db688892:fakedir1/3rdparty/edbee-lib \
			martin-eden:lua_code_formatter:4aa25029eae867840e6c06c7b075f4b690dd2ec2:fakedir2/3rdparty/lcf \
			julian-go:qt-tags-widget:26f177cbcebe66fdc3e8daed4d0984a7f60f3431:fakedir3/3rdparty/qt-tags-widget \
			getsentry:sentry-native:c0e5f0705da3853ff548c7ece77d639a20e1d8f5:fakedir5/3rdparty/sentry-native

USE_QT=			base 5compat multimedia tools
USE_GL=			gl opengl glu

# USES=cmake related variables ##--------------------------------------------------------------------------
#
### Make block ##------------------------------------------------------------------------------------------
#
### conflicts ##-------------------------------------------------------------------------------------------
CONFLICTS=		Mudlet mudlet
### wrksrc block ##----------------------------------------------------------------------------------------
#
### packaging list block ##--------------------------------------------------------------------------------
#
### options definitions ##---------------------------------------------------------------------------------
#
### options descriptions ##--------------------------------------------------------------------------------
#
### options helpers ##-------------------------------------------------------------------------------------
#
.include 

post-stage:
	${MKDIR} ${STAGEDIR}${LOCALBASE}/share/lua/5.1/
	cd ${STAGEDIR}${LOCALBASE} && ${LOCALBASE}/bin/luarocks54 --tree= --lua-version 5.1 install luautf8
	cd ${STAGEDIR}${LOCALBASE} && ${LOCALBASE}/bin/luarocks54 --tree= --lua-version 5.1 install luafilesystem
	cd ${STAGEDIR}${LOCALBASE} && ${LOCALBASE}/bin/luarocks54 --tree= --lua-version 5.1 install lua-zip
	cd ${STAGEDIR}${LOCALBASE} && ${LOCALBASE}/bin/luarocks54 --tree= --lua-version 5.1 install luasql-sqlite3
	cd ${STAGEDIR}${LOCALBASE} && ${LOCALBASE}/bin/luarocks54 --tree= --lua-version 5.1 install lrexlib-pcre2
	cd ${STAGEDIR}${LOCALBASE} && ${LOCALBASE}/bin/luarocks54 --tree= --lua-version 5.1 install lpeg
	cd ${STAGEDIR}${LOCALBASE} && ${LOCALBASE}/bin/luarocks54 --tree= --lua-version 5.1 install lua-yajl
	cp -R /usr/local/share/lua/lib/lua/5.1/* /usr/local/lib/lua/5.1/

# The above is definitely weird but I believe everything gets placed where it must for mudlet features to work.

#----------------------------------------------------------------------

.include 

What took me the longest to perfect were the tuples and getting them to be placed in the correct locations.  What baffles me the most about this, is how it was successful, since I used a series of numbered 'fakedir' directories which are entirely ignored.  Should those "fakedir" entries actually be some kind of other place-holder word?  What I did works for now, when it breaks I will update it to the proper method.  The handbook is less than clear about handling a git submodule arrangement like this.  A REAL example would be much more helpful than foo and bar and all that confusing stuff because I could go to the real port to see the whole process, step through the directories created and truly understand it.  One other thing that doesn't seem to be mentioned in the handbook anywhere that I could locate, is how to have a dependency upon a specific flavored port.  I had to depend upon one of the two possible flavors for a dependency libqt6keychain.so:security/qtkeychain@qt6 which I happened to partially guess after trying other things that generated errors.

I've lightly tested Mudlet to see what it is like, it works fine but I didn't try to play any MUD games yet.  The work I have done to take a known FreeBSD build mentioned on the Mudlet wiki to a Makefile usable in our ports tree but that mine tracks upstream commits instead of release versions.  You are very welcome, whomever is interested, to take all my efforts, lightly revise them for release version tracking, possibly correct any of my style or technique mistakes, and maybe create a better method for the luarocks portion.  There is an easy way to install Mudlet now, the rest is up to a volunteer with my blessing.  My success would never have been possible without the work of erikarn (adrian@freebsd.org).

Frequently viewed this week