Folio icon

Folio -----

Modular fluent paginated inventory GUI framework — pagination, animations, async loading, anvil-sear



Folio — Documentation

1. Layouts

Mask layout — text blocks
Code (Java):

. layout (MaskLayout. of ( "" "
       BBBBBBBBB
       B XXXXX B
       B XXXXX B
       B XXXXX B
       BBBBBBBBB
       "
"" ) )
 
Char Meaning
X item slot (consumed by auto-pagination)
B border (theme material, no click)
. empty slot
< > | F L P reserved markers — accessible via MaskLayout.slotOf(...)


Whitespace is ignored — write the pattern naturally.

Grid layout
Code (Java):

. layout ( GridLayout. bordered ( ) )           // border + bottom row reserved
. layout ( new GridLayout ( true, 0 ) )         // border only
. layout ( GridLayout. plain ( ) )             // 9×rows, no border
 
Linear layout
Code (Java):

. layout (LinearLayout. full ( ) )             // 0..size
. layout (LinearLayout. withNavRow ( ) )       // 0..size-9 (last row reserved)
 
[HR][/HR]

2. Click handlers
Code (Java):

InventoryItem. builder ( )
       . material (Material. DIAMOND_SWORD )
       . name ( Component. text ( "Excalibur" ) )
       . onLeftClick (ctx   -> { upgrade (ctx. player ( ) ) ; return ClickResult. refresh ( ) ; } )
       . onRightClick (ctx -> { describe (ctx. player ( ) ) ; return ClickResult. cancel ( ) ; } )
       . onShiftClick (ctx -> { sell (ctx. player ( ) ) ;     return ClickResult. refresh ( ) ; } )
       . onClickAsync (ctx -> { db. recordClick (... ) ;     return ClickResult. cancel ( ) ; } )
       . build ( ) ;
 
ClickResult is a sealed interface — exhaustive switch:
Code (Java):

switch (handler. handle (ctx ) ) {
    case ClickResult. Allow a     -> { /* let the underlying click through */ }
    case ClickResult. Cancel c   -> { /* default — already cancelled */ }
    case ClickResult. Close cl   -> ctx. player ( ). closeInventory ( ) ;
    case ClickResult. Refresh r   -> session. refresh ( ) ;
    case ClickResult. Navigate n -> session. switchPage (n. targetPage ( ) ) ;
}
 
Click events bubble: item handler → item global → page global, stopping at the first non-Cancel result.

[HR][/HR]

3. Animations
Code (Java):

AnimatedItem loading = AnimatedItem. builder ( ). frames ( List. of (
        Frame. of (named (Material. RED_WOOL,     "Loading" ), 4 ),
        Frame. of (named (Material. YELLOW_WOOL, "Loading" ), 4 ),
        Frame. of (named (Material. LIME_WOOL,   "Loading" ), 4 )
) ). build ( ) ;

InventoryPage. builder ( )
       . refreshEvery (Duration. ofMillis ( 50 ) )   // 1 tick — clamped to >= 1
       . fixed ( 13, loading )
       ...
 
Diff-renderer only writes slots whose stack actually changed — no flicker.

[HR][/HR]

4. Async loading
Code (Java):

AsyncSpec spec = AsyncSpec. of ( ( ) -> CompletableFuture. supplyAsync ( ( ) ->
               database. loadShopOffers (playerId )
                       . stream ( )
                       . map ( this ::toItem )
                       . toList ( ) ) )
       . placeholder (InventoryItem. of (named (Material. CLOCK, "§7Loading…" ) ) )
       . errorItem (InventoryItem. of (named (Material. BARRIER, "§cError" ) ) )
       . cache (Duration. ofMinutes ( 5 ) )
       . cacheKey ( "shop:" + playerId )
       . build ( ) ;

InventoryPage. builder ( ). asyncItems (spec )...
 
Inventory opens instantly with placeholders. When the future resolves, items pop in on the main thread (diffed). If the player closes mid-load, the pending future is soft-discarded.

[HR][/HR]

5. Filter & live search
Code (Java):

// Predicate-based filter
Filter cheap = (item, viewer ) -> price (item ) < viewer. getLevel ( ) ;
session. setFilter (cheap. and (byCategory ( "weapons" ) ) ) ;

// Anvil-GUI live search
SearchPrompt. builder (plugin, player )
       . title ( Component. text ( "Search", NamedTextColor. GOLD ) )
       . initial ( "" )
       . onConfirm (query -> {
            String q = query. toLowerCase ( ) ;
           Filter f = (i, v ) -> i. render (v ). getType ( ). name ( ). toLowerCase ( ). contains (q ) ;
           var s = registry. open (player, paged ) ;
            ( (InventorySessionImpl ) s ). setFilter (f ) ;
        } )
       . onCancel ( ( ) -> registry. open (player, paged ) )
       . open ( ) ;
 
[HR][/HR]

6. Persistence
Code (Java):

// 1) Register a named factory
registry. registerNamed ( "shop", ( ) -> PagedInventory. paginate (buildShopPage ( ) ) ) ;

// 2) Mark the page persistent
InventoryPage. builder ( )
       . persistent ( "shop" )     // saved to PDC on close
       ...
 
After a re-login, Folio reads the player's PDC and reopens the same page on the same index — automatic.

[HR][/HR]

7. Hotbar keybinds
Code (Java):

InventoryPage. builder ( )
       . hotbarBind ( 1, ctx -> { ctx. player ( ). sendMessage ( "Quick action 1" ) ; return ClickResult. cancel ( ) ; } )
       . hotbarBind ( 2, ctx -> ClickResult. refresh ( ) )
       . hotbarBind ( 3, ctx -> ClickResult. close ( ) )
       ...
 
While the player hovers any slot in your top inventory, pressing 1..9 fires the matching handler.

[HR][/HR]

8. Form / input slots
Code (Java):

InventoryPage. builder ( )
       . inputSlots ( 11, 13, 15 )                   // player can place items here
       . fixed ( 22, InventoryItem. builder ( )
               . material (Material. EMERALD ). name ( Component. text ( "Confirm" ) )
               . onClick (ctx -> {
                   var inv = ctx. session ( ). inventoryFor (ctx. player ( ) ) ;
                   var ingredients = List. of (inv. getItem ( 11 ), inv. getItem ( 13 ), inv. getItem ( 15 ) ) ;
                    if (ingredients. stream ( ). anyMatch (i -> i == null || i. getType ( ). isAir ( ) ) ) {
                       ctx. player ( ). sendMessage ( "Fill all 3 slots!" ) ;
                        return ClickResult. cancel ( ) ;
                    }
                   craft (ctx. player ( ), ingredients ) ;
                   inv. setItem ( 11, null ) ; inv. setItem ( 13, null ) ; inv. setItem ( 15, null ) ;
                    return ClickResult. refresh ( ) ;
                } )
               . build ( ) )
       . build ( ) ;
 
On close, leftover items in input slots are returned to the player's inventory (or dropped if full) — players can't lose stuff.

[HR][/HR]

9. Multi-player (shared) sessions
Code (Java):

InventoryPage. builder ( )
       . sessionMode (SessionMode. SHARED )         // one Inventory, many viewers
       . title ( Component. text ( "Auction" ) )
       ...
 
All viewers see the same Inventory — clicks by one are visible to all. Page cursor is also shared.

[HR][/HR]

10. Theme
Code (Java):

InventoryTheme theme = InventoryTheme. builder ( )
       . borderMaterial (Material. BLACK_STAINED_GLASS_PANE )
       . openSound (Sound. UI_TOAST_IN )
       . clickSound (Sound. UI_BUTTON_CLICK )
       . pageSwitchSound (Sound. ITEM_BOOK_PAGE_TURN )
       . deniedSound (Sound. BLOCK_NOTE_BLOCK_BASS )
       . pageIndicatorTemplate ( "<dark_gray>« <white><current><dark_gray>/<white><total> <dark_gray>»" )
       . build ( ) ;

InventoryPage. builder ( ). theme (theme )...
 
Indicator template is MiniMessage with <current> and <total> tokens.

[HR][/HR]

11. Permissions
Code (Java):

InventoryItem. builder ( )
       . visibleIf (PermissionGate. hasPermission ( "shop.see-rare" ) )
       . clickableIf (PermissionGate. all (
               PermissionGate. hasPermission ( "shop.buy" ),
               p -> economy. balance (p ) > 0 ) )
       ...

InventoryPage. builder ( )
       . requirePermission ( "shop.use" )           // page-level gate
       ...
 
Hidden items are excluded from layout placement and don't count toward pagination.

[HR][/HR]

12. Complete example — all features in one page
Code (Java):

InventoryPage page = InventoryPage. builder ( )
       . title ( Component. text ( "Big Demo", NamedTextColor. GOLD ) )
       . rows ( 6 )
       . layout (MaskLayout. of ( "" "
               BBBBBBBBB
               BXXXXXXXB
               BXXXXXXXB
               BXXXXXXXB
               BXXXXXXXB
               BBBBBBBBB
               "
"" ) )
       . theme (InventoryTheme. dark ( ) )
       . navigation (NavigationButtons. builder ( )
               . firstSlot ( 45 ). previousSlot ( 46 ). pageIndicatorSlot ( 48 )
               . nextSlot ( 50 ). lastSlot ( 51 ). build ( ) )
       . items (offers )
       . filter ( (i, v ) -> v. hasPermission ( "shop.see-all" ) || !rare (i ) )
       . asyncItems (databaseSpec )
       . refreshEvery (Duration. ofSeconds ( 1 ) )
       . persistent ( "big-demo" )
       . hotbarBind ( 1, ctx -> ClickResult. refresh ( ) )
       . hotbarBind ( 9, ctx -> ClickResult. close ( ) )
       . inputSlots ( 52 )                           // single "drop here to sell" slot
       . sessionMode (SessionMode. PER_PLAYER )
       . requirePermission ( "shop.use" )
       . onClick (ctx -> ClickResult. cancel ( ) )   // fallback
       . build ( ) ;

registry. open (player, PagedInventory. paginate (page ) ) ;
 
Resource Information
Author:
----------
Total Downloads: 9
First Release: Apr 26, 2026
Last Update: Apr 26, 2026
Category: ---------------
All-Time Rating:
0 ratings
Version -----
Released: --------------------
Downloads: ------
Version Rating:
----------------------
-- ratings