mirror of
https://github.com/Drezil/dear-imgui.hs.git
synced 2024-11-22 16:57:00 +00:00
Implement ImGuiListClipper (#100)
* Added DearImGui.Raw.ImGuiListClipper * Added the DearImGui.withListClipper bracket * Added dependency for `vector` * Added the test in the Main.hs
This commit is contained in:
parent
8ee82476dc
commit
4ecf62ac9e
16
Main.hs
16
Main.hs
@ -7,6 +7,7 @@ module Main (main) where
|
|||||||
|
|
||||||
import Control.Monad
|
import Control.Monad
|
||||||
import Data.IORef
|
import Data.IORef
|
||||||
|
import qualified Data.Vector as Vector
|
||||||
import DearImGui
|
import DearImGui
|
||||||
import DearImGui.OpenGL3
|
import DearImGui.OpenGL3
|
||||||
import DearImGui.SDL
|
import DearImGui.SDL
|
||||||
@ -131,6 +132,21 @@ loop window checked color slider r pos size' selected tab1Ref tab2Ref = do
|
|||||||
|
|
||||||
endChild
|
endChild
|
||||||
|
|
||||||
|
text "ListClipper"
|
||||||
|
withChildOpen "##fixed" (ImVec2 0 200) True ImGuiWindowFlags_None do
|
||||||
|
let lotsOfItems = Vector.generate 50 (mappend "Item " . show)
|
||||||
|
withListClipper Nothing lotsOfItems text
|
||||||
|
|
||||||
|
text "ListClipper, Haskell-powered"
|
||||||
|
withChildOpen "##infinite" (ImVec2 0 200) True ImGuiWindowFlags_None do
|
||||||
|
let infiniteItems = map (mappend "Item " . show) [0 :: Int ..]
|
||||||
|
withListClipper Nothing infiniteItems text
|
||||||
|
|
||||||
|
text "Ethereal ListClipper"
|
||||||
|
withChildOpen "##ethereal" (ImVec2 0 200) True ImGuiWindowFlags_None do
|
||||||
|
withListClipper Nothing (ClipRange (0 :: Int) 1000) $
|
||||||
|
text . mappend "Item " . show
|
||||||
|
|
||||||
plotHistogram "A histogram" [ 10, 10, 20, 30, 90 ]
|
plotHistogram "A histogram" [ 10, 10, 20, 30, 90 ]
|
||||||
|
|
||||||
colorPicker3 "Test" color
|
colorPicker3 "Test" color
|
||||||
|
@ -93,6 +93,7 @@ library
|
|||||||
DearImGui
|
DearImGui
|
||||||
DearImGui.Raw
|
DearImGui.Raw
|
||||||
DearImGui.Raw.DrawList
|
DearImGui.Raw.DrawList
|
||||||
|
DearImGui.Raw.ListClipper
|
||||||
other-modules:
|
other-modules:
|
||||||
DearImGui.Context
|
DearImGui.Context
|
||||||
DearImGui.Enums
|
DearImGui.Enums
|
||||||
@ -117,6 +118,7 @@ library
|
|||||||
, inline-c-cpp
|
, inline-c-cpp
|
||||||
, StateVar
|
, StateVar
|
||||||
, unliftio
|
, unliftio
|
||||||
|
, vector
|
||||||
|
|
||||||
if flag(opengl2)
|
if flag(opengl2)
|
||||||
exposed-modules:
|
exposed-modules:
|
||||||
@ -241,7 +243,7 @@ executable test
|
|||||||
if (!flag(examples) || !flag(sdl) || !flag(opengl2))
|
if (!flag(examples) || !flag(sdl) || !flag(opengl2))
|
||||||
buildable: False
|
buildable: False
|
||||||
else
|
else
|
||||||
build-depends: base, sdl2, gl, dear-imgui
|
build-depends: base, sdl2, gl, dear-imgui, vector
|
||||||
|
|
||||||
executable glfw
|
executable glfw
|
||||||
main-is: Main.hs
|
main-is: Main.hs
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
{-# LANGUAGE FlexibleContexts #-}
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
{-# LANGUAGE FlexibleInstances #-}
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
{-# LANGUAGE LambdaCase #-}
|
{-# LANGUAGE LambdaCase #-}
|
||||||
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
{-# LANGUAGE NamedFieldPuns #-}
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
{-# LANGUAGE PatternSynonyms #-}
|
{-# LANGUAGE PatternSynonyms #-}
|
||||||
@ -255,6 +256,11 @@ module DearImGui
|
|||||||
|
|
||||||
-- * Utilities
|
-- * Utilities
|
||||||
|
|
||||||
|
-- ** ListClipper
|
||||||
|
, withListClipper
|
||||||
|
, ClipItems(..)
|
||||||
|
, ClipRange(..)
|
||||||
|
|
||||||
-- ** Miscellaneous
|
-- ** Miscellaneous
|
||||||
, Raw.getBackgroundDrawList
|
, Raw.getBackgroundDrawList
|
||||||
, Raw.getForegroundDrawList
|
, Raw.getForegroundDrawList
|
||||||
@ -298,7 +304,12 @@ import UnliftIO (MonadUnliftIO)
|
|||||||
import UnliftIO.Exception (bracket, bracket_)
|
import UnliftIO.Exception (bracket, bracket_)
|
||||||
|
|
||||||
import qualified DearImGui.Raw as Raw
|
import qualified DearImGui.Raw as Raw
|
||||||
|
import qualified DearImGui.Raw.ListClipper as Raw.ListClipper
|
||||||
|
|
||||||
|
-- vector
|
||||||
|
import qualified Data.Vector as V
|
||||||
|
import qualified Data.Vector.Storable as VS
|
||||||
|
import qualified Data.Vector.Unboxed as VU
|
||||||
|
|
||||||
-- | Get the compiled version string e.g. "1.80 WIP" (essentially the value for
|
-- | Get the compiled version string e.g. "1.80 WIP" (essentially the value for
|
||||||
-- @IMGUI_VERSION@ from the compiled version of @imgui.cpp@).
|
-- @IMGUI_VERSION@ from the compiled version of @imgui.cpp@).
|
||||||
@ -1698,3 +1709,86 @@ addFontFromFileTTF font size = liftIO do
|
|||||||
if castPtr ptr == nullPtr
|
if castPtr ptr == nullPtr
|
||||||
then Nothing
|
then Nothing
|
||||||
else Just res
|
else Just res
|
||||||
|
|
||||||
|
-- | Clips a large list of items
|
||||||
|
--
|
||||||
|
-- The requirements on @a@ are that they are all of the same height.
|
||||||
|
withListClipper :: (ClipItems t a, MonadUnliftIO m) => Maybe Float -> t a -> (a -> m ()) -> m ()
|
||||||
|
withListClipper itemHeight items action =
|
||||||
|
bracket
|
||||||
|
(liftIO $ throwIfNull "withListClipper: ListClipper allocation failed" Raw.ListClipper.new)
|
||||||
|
Raw.ListClipper.delete
|
||||||
|
step
|
||||||
|
where
|
||||||
|
itemHeight' = maybe (-1.0) CFloat itemHeight
|
||||||
|
itemCount' = maybe maxBound fromIntegral (itemCount items)
|
||||||
|
|
||||||
|
step clipper = do
|
||||||
|
Raw.ListClipper.begin clipper itemCount' itemHeight'
|
||||||
|
go clipper
|
||||||
|
|
||||||
|
go clipper = do
|
||||||
|
doStep <- Raw.ListClipper.step clipper
|
||||||
|
when doStep do
|
||||||
|
let
|
||||||
|
startIndex = fromIntegral $ Raw.ListClipper.displayStart clipper
|
||||||
|
endIndex = fromIntegral $ Raw.ListClipper.displayEnd clipper
|
||||||
|
stepItems action $
|
||||||
|
clipItems startIndex endIndex items
|
||||||
|
|
||||||
|
go clipper
|
||||||
|
|
||||||
|
-- | Containers usable with 'ListClipper'.
|
||||||
|
class ClipItems t a where
|
||||||
|
itemCount :: t a -> Maybe Int
|
||||||
|
clipItems :: Int -> Int -> t a -> t a
|
||||||
|
stepItems :: Monad m => (a -> m ()) -> t a -> m ()
|
||||||
|
|
||||||
|
-- | Unbounded stream of items.
|
||||||
|
instance ClipItems [] a where
|
||||||
|
itemCount = const Nothing
|
||||||
|
|
||||||
|
clipItems displayStart displayEnd =
|
||||||
|
take (displayEnd - displayStart) . drop displayStart
|
||||||
|
|
||||||
|
stepItems = mapM_
|
||||||
|
|
||||||
|
instance ClipItems V.Vector a where
|
||||||
|
itemCount = Just . V.length
|
||||||
|
|
||||||
|
clipItems displayStart displayEnd =
|
||||||
|
V.slice displayStart (displayEnd - displayStart)
|
||||||
|
|
||||||
|
stepItems = V.mapM_
|
||||||
|
|
||||||
|
instance Storable a => ClipItems VS.Vector a where
|
||||||
|
itemCount = Just . VS.length
|
||||||
|
|
||||||
|
clipItems displayStart displayEnd =
|
||||||
|
VS.slice displayStart (displayEnd - displayStart)
|
||||||
|
|
||||||
|
stepItems = VS.mapM_
|
||||||
|
|
||||||
|
instance VU.Unbox a => ClipItems VU.Vector a where
|
||||||
|
itemCount = Just . VU.length
|
||||||
|
|
||||||
|
clipItems displayStart displayEnd =
|
||||||
|
VU.slice displayStart (displayEnd - displayStart)
|
||||||
|
|
||||||
|
stepItems = VU.mapM_
|
||||||
|
|
||||||
|
-- | ClipList helper for arbitrary unmaterialized ranges.
|
||||||
|
data ClipRange a = ClipRange a a
|
||||||
|
deriving (Eq, Ord, Show)
|
||||||
|
|
||||||
|
instance (Ord a, Enum a, Num a) => ClipItems ClipRange a where
|
||||||
|
itemCount (ClipRange _begin end) =
|
||||||
|
Just $ fromEnum end
|
||||||
|
|
||||||
|
clipItems clipBegin clipEnd (ClipRange oldBegin oldEnd) =
|
||||||
|
ClipRange
|
||||||
|
(toEnum $ max clipBegin $ fromEnum oldBegin)
|
||||||
|
(toEnum $ min clipEnd $ fromEnum oldEnd)
|
||||||
|
|
||||||
|
stepItems action (ClipRange start end) =
|
||||||
|
mapM_ action [start .. end - 1]
|
||||||
|
@ -37,5 +37,6 @@ imguiContext = mempty
|
|||||||
, ( TypeName "ImDrawList", [t| ImDrawList |] )
|
, ( TypeName "ImDrawList", [t| ImDrawList |] )
|
||||||
, ( TypeName "ImGuiContext", [t| ImGuiContext |] )
|
, ( TypeName "ImGuiContext", [t| ImGuiContext |] )
|
||||||
, ( TypeName "ImFont", [t| ImFont |] )
|
, ( TypeName "ImFont", [t| ImFont |] )
|
||||||
|
, ( TypeName "ImGuiListClipper", [t| ImGuiListClipper |] )
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
149
src/DearImGui/Raw/ListClipper.hs
Normal file
149
src/DearImGui/Raw/ListClipper.hs
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
{-# LANGUAGE BlockArguments #-}
|
||||||
|
{-# LANGUAGE DuplicateRecordFields #-}
|
||||||
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
{-# LANGUAGE LambdaCase #-}
|
||||||
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE PatternSynonyms #-}
|
||||||
|
{-# LANGUAGE QuasiQuotes #-}
|
||||||
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
|
{-# LANGUAGE ViewPatterns #-}
|
||||||
|
|
||||||
|
{-| Helper: Manually clip large list of items.
|
||||||
|
|
||||||
|
If you are submitting lots of evenly spaced items and you have a random access to the list,
|
||||||
|
you can perform coarse clipping based on visibility to save yourself from processing those items at all.
|
||||||
|
|
||||||
|
The clipper calculates the range of visible items and advance the cursor to compensate for the non-visible items we have skipped.
|
||||||
|
|
||||||
|
Dear ImGui already clips items based on their bounds but it needs to measure text size to do so,
|
||||||
|
whereas manual coarse clipping before submission makes this cost and your own data fetching/submission cost almost null.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
@
|
||||||
|
clipper <- ListClipper.new
|
||||||
|
ListClipper.begin clipper 1000 -- We have 1000 elements, evenly spaced.
|
||||||
|
whileTrue (ListClipper.step clipper) $
|
||||||
|
start <- ListClipper.displayStart clipper
|
||||||
|
end <- ListClipper.displayEnd clipper
|
||||||
|
for_ [start .. end] \ix ->
|
||||||
|
ImGui.text $ "line number " <> show ix
|
||||||
|
@
|
||||||
|
|
||||||
|
Generally what happens is:
|
||||||
|
|
||||||
|
* Clipper lets you process the first element (DisplayStart = 0, DisplayEnd = 1) regardless of it being visible or not.
|
||||||
|
* User code submit one element.
|
||||||
|
* Clipper can measure the height of the first element
|
||||||
|
* Clipper calculate the actual range of elements to display based on the current clipping rectangle,
|
||||||
|
position the cursor before the first visible element.
|
||||||
|
* User code submit visible elements.
|
||||||
|
-}
|
||||||
|
|
||||||
|
module DearImGui.Raw.ListClipper
|
||||||
|
( ListClipper
|
||||||
|
, new
|
||||||
|
, delete
|
||||||
|
, begin
|
||||||
|
, displayStart
|
||||||
|
, displayEnd
|
||||||
|
, step
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Control.Monad.IO.Class
|
||||||
|
( MonadIO, liftIO )
|
||||||
|
import Foreign hiding (new)
|
||||||
|
import Foreign.C
|
||||||
|
import System.IO.Unsafe (unsafePerformIO)
|
||||||
|
|
||||||
|
-- dear-imgui
|
||||||
|
import DearImGui.Context
|
||||||
|
( imguiContext )
|
||||||
|
import DearImGui.Structs
|
||||||
|
( ImGuiListClipper )
|
||||||
|
|
||||||
|
-- inline-c
|
||||||
|
import qualified Language.C.Inline as C
|
||||||
|
|
||||||
|
-- inline-c-cpp
|
||||||
|
import qualified Language.C.Inline.Cpp as Cpp
|
||||||
|
|
||||||
|
C.context (Cpp.cppCtx <> C.bsCtx <> imguiContext)
|
||||||
|
C.include "imgui.h"
|
||||||
|
Cpp.using "namespace ImGui"
|
||||||
|
|
||||||
|
|
||||||
|
-- | @ImGuiListClipper@ object handle.
|
||||||
|
type ListClipper = Ptr ImGuiListClipper
|
||||||
|
|
||||||
|
|
||||||
|
-- | Create a new 'ListClipper' instance.
|
||||||
|
new :: (MonadIO m) => m ListClipper
|
||||||
|
new = liftIO do
|
||||||
|
[C.block|
|
||||||
|
ImGuiListClipper* {
|
||||||
|
return IM_NEW(ImGuiListClipper);
|
||||||
|
}
|
||||||
|
|]
|
||||||
|
|
||||||
|
-- | Destroy 'ListClipper' instance.
|
||||||
|
delete :: (MonadIO m) => ListClipper -> m ()
|
||||||
|
delete clipper = liftIO do
|
||||||
|
[C.block|
|
||||||
|
void {
|
||||||
|
IM_DELETE($(ImGuiListClipper* clipper));
|
||||||
|
}
|
||||||
|
|]
|
||||||
|
|
||||||
|
|
||||||
|
-- | ListClipper setup
|
||||||
|
--
|
||||||
|
-- @items_count@: Use 'maxBound' if you don't know how many items you have
|
||||||
|
-- (in which case the cursor won't be advanced in the final step).
|
||||||
|
--
|
||||||
|
-- @items_height@: Use -1.0f to be calculated automatically on first step.
|
||||||
|
-- Otherwise pass in the distance between your items, typically
|
||||||
|
-- 'getTextLineHeightWithSpacing' or 'getFrameHeightWithSpacing'.
|
||||||
|
--
|
||||||
|
-- Wraps @ListClipper::Begin()@.
|
||||||
|
begin :: (MonadIO m) => ListClipper -> CInt -> CFloat -> m ()
|
||||||
|
begin clipper items_count items_height = liftIO do
|
||||||
|
[C.block|
|
||||||
|
void {
|
||||||
|
$(ImGuiListClipper* clipper)->Begin($(int items_count), $(float items_height));
|
||||||
|
}
|
||||||
|
|]
|
||||||
|
|
||||||
|
-- | An accessor for @ListClipper::Begin@
|
||||||
|
displayStart :: ListClipper -> CInt
|
||||||
|
displayStart clipper = unsafePerformIO do
|
||||||
|
[C.exp|
|
||||||
|
int {
|
||||||
|
$(ImGuiListClipper* clipper)->DisplayStart
|
||||||
|
}
|
||||||
|
|]
|
||||||
|
|
||||||
|
-- | An accessor for @ListClipper::DisplayStart@
|
||||||
|
displayEnd :: ListClipper -> CInt
|
||||||
|
displayEnd clipper = unsafePerformIO
|
||||||
|
[C.exp|
|
||||||
|
int {
|
||||||
|
$(ImGuiListClipper* clipper)->DisplayEnd
|
||||||
|
}
|
||||||
|
|]
|
||||||
|
|
||||||
|
|
||||||
|
-- | Call until it returns 'False'.
|
||||||
|
--
|
||||||
|
-- The 'displayStart'/'displayEnd' fields will be set and you can process/draw those items.
|
||||||
|
--
|
||||||
|
-- Wraps @ListClipper::Step()@.
|
||||||
|
step :: (MonadIO m) => ListClipper -> m Bool
|
||||||
|
step clipper = liftIO do
|
||||||
|
(0 /=) <$> [C.block|
|
||||||
|
bool {
|
||||||
|
return $(ImGuiListClipper* clipper)->Step();
|
||||||
|
}
|
||||||
|
|]
|
@ -83,5 +83,8 @@ data ImFont
|
|||||||
-- | Opaque DrawList handle.
|
-- | Opaque DrawList handle.
|
||||||
data ImDrawList
|
data ImDrawList
|
||||||
|
|
||||||
|
-- | 'DearImGui.Raw.ListClipper.ListClipper' pointer tag.
|
||||||
|
data ImGuiListClipper
|
||||||
|
|
||||||
-- | 32-bit unsigned integer (often used to store packed colors).
|
-- | 32-bit unsigned integer (often used to store packed colors).
|
||||||
type ImU32 = Word32
|
type ImU32 = Word32
|
||||||
|
Loading…
Reference in New Issue
Block a user