Karya, built on 2023-08-29T07:47:28 (patch 7a412d5d6ba4968ca4155ef276a062ccdeb9109a)
Safe HaskellSafe-Inferred

Ui.Ui

Description

The overall UI state is described here. This is an immutable data structure that contains all the tracks, rulers, note data, and so forth. It exports a StateT monad for modification and access.

Since the same block may have >=0 views, and a single track may appear in >=0 blocks, these are stored as IDs rather than directly in their containers. Using explicit references introduces all the usual problems with pointers like invalid references and unreferenced data. The latter is actually a feature (e.g. having a block with no associated view is perfectly normal), but the former is a pain. To ease the pain, IDs should only be created via the monadic create_* interface in this module, even though I'm forced to export their constructors to avoid circular imports. There may still be problems with IDs from one State being applied to a different State (likely an older and newer version of the same State), but I'll deal with that when I get there.

A higher level interface (e.g. Cmd.Create) may ease this by automatically creating objects with automatically generated IDs.

Synopsis

Documentation

data State Source #

Score state. When you save a score, this is what is saved to disk.

Instances

Instances details
Show State Source # 
Instance details

Defined in Ui.Ui

Methods

showsPrec :: Int -> State -> ShowS #

show :: State -> String #

showList :: [State] -> ShowS #

DeepSeq.NFData State Source # 
Instance details

Defined in Ui.Ui

Methods

rnf :: State -> () #

Eq State Source # 
Instance details

Defined in Ui.Ui

Methods

(==) :: State -> State -> Bool #

(/=) :: State -> State -> Bool #

Pretty.Pretty State Source # 
Instance details

Defined in Ui.Ui

Serialize State Source # 
Instance details

Defined in Cmd.Serialize

create :: IO State Source #

Like empty, but the state is initialized with the current creation time.

clear :: State -> State Source #

Clear out data that shouldn't be saved.

address types

data Track Source #

Address a track in a block. This is similar to a TrackId, except it doesn't guarantee that the track is an event track.

Instances

Instances details
Show Track Source # 
Instance details

Defined in Ui.Ui

Methods

showsPrec :: Int -> Track -> ShowS #

show :: Track -> String #

showList :: [Track] -> ShowS #

Eq Track Source # 
Instance details

Defined in Ui.Ui

Methods

(==) :: Track -> Track -> Bool #

(/=) :: Track -> Track -> Bool #

Pretty.Pretty Track Source # 
Instance details

Defined in Ui.Ui

data Range Source #

A position on a track that can be indicated on the UI. Its Pretty instance emits a string, which if logged or copy-pasted into the REPL, will cause that section of score to be highlighted.

Instances

Instances details
Show Range Source # 
Instance details

Defined in Ui.Ui

Methods

showsPrec :: Int -> Range -> ShowS #

show :: Range -> String #

showList :: [Range] -> ShowS #

Eq Range Source # 
Instance details

Defined in Ui.Ui

Methods

(==) :: Range -> Range -> Bool #

(/=) :: Range -> Range -> Bool #

Pretty.Pretty Range Source # 
Instance details

Defined in Ui.Ui

data TrackInfo Source #

Summary information on a Track.

Instances

Instances details
Show TrackInfo Source # 
Instance details

Defined in Ui.Ui

Eq TrackInfo Source # 
Instance details

Defined in Ui.Ui

Pretty.Pretty TrackInfo Source # 
Instance details

Defined in Ui.Ui

StateT monad

class (Applicative m, Monad m) => M m Source #

Monads implementing this class can call the UI state functions directly.

Minimal complete definition

get, unsafe_put, damage, get_damage, throw_error

Instances

Instances details
Monad m => M (CmdT m) Source #

And to the UI state operations.

Instance details

Defined in Cmd.Cmd

Monad m => M (StateT m) Source # 
Instance details

Defined in Ui.Ui

M m => M (Except.ExceptT exc m) Source # 
Instance details

Defined in Ui.Ui

M m => M (State.StateT state m) Source # 
Instance details

Defined in Ui.Ui

data StateT m a Source #

Instances

Instances details
MonadTrans StateT Source # 
Instance details

Defined in Ui.Ui

Methods

lift :: Monad m => m a -> StateT m a #

Monad m => MonadError Error (StateT m) Source # 
Instance details

Defined in Ui.Ui

Methods

throwError :: Error -> StateT m a #

catchError :: StateT m a -> (Error -> StateT m a) -> StateT m a #

MonadIO m => MonadIO (StateT m) Source # 
Instance details

Defined in Ui.Ui

Methods

liftIO :: IO a -> StateT m a #

Monad m => Applicative (StateT m) Source # 
Instance details

Defined in Ui.Ui

Methods

pure :: a -> StateT m a #

(<*>) :: StateT m (a -> b) -> StateT m a -> StateT m b #

liftA2 :: (a -> b -> c) -> StateT m a -> StateT m b -> StateT m c #

(*>) :: StateT m a -> StateT m b -> StateT m b #

(<*) :: StateT m a -> StateT m b -> StateT m a #

Functor m => Functor (StateT m) Source # 
Instance details

Defined in Ui.Ui

Methods

fmap :: (a -> b) -> StateT m a -> StateT m b #

(<$) :: a -> StateT m b -> StateT m a #

Monad m => Monad (StateT m) Source # 
Instance details

Defined in Ui.Ui

Methods

(>>=) :: StateT m a -> (a -> StateT m b) -> StateT m b #

(>>) :: StateT m a -> StateT m b -> StateT m b #

return :: a -> StateT m a #

Monad m => M (StateT m) Source # 
Instance details

Defined in Ui.Ui

Monad m => LogMonad (UiLogT m) Source # 
Instance details

Defined in Ui.UiLog

Methods

write :: Msg -> UiLogT m () Source #

type StateId a = StateT Identity a Source #

Just a convenient abbreviation.

get :: M m => m State Source #

unsafe_put :: M m => State -> m () Source #

This directly modifies the state, and can break internal invariants. put is slower but safer since it checks those invariants.

damage :: M m => Update.UiDamage -> m () Source #

throw_error :: M m => Error -> m a Source #

throw :: (CallStack.Stack, M m) => Text -> m a Source #

run :: Monad m => State -> StateT m a -> m (Either Error (a, State, Update.UiDamage)) Source #

Run the given StateT with the given initial state, and return a new state along with updates. Normally updates are produced by diff, but for efficiency updates to track data are accumulated when they are actually made. All the UI needs is a TrackTime range to redraw in, and redrawing the whole track isn't that expensive.

See the StateStack comment for more.

eval :: State -> StateId a -> Either Error a Source #

A form of run that returns only the val and automatically runs in Identity.

eval_rethrow :: M m => Text -> State -> StateId a -> m a Source #

exec_rethrow :: M m => Text -> State -> StateId a -> m State Source #

gets :: M m => (State -> a) -> m a Source #

unsafe_modify :: M m => (State -> State) -> m () Source #

As with unsafe_put, this directly modifies the state. modify is the safe version.

put :: M m => State -> m () Source #

TODO verify

This updates all tracks because I don't know what you modified in there.

modify :: M m => (State -> State) -> m () Source #

An arbitrary modify. It's unsafe because it doesn't check internal invariants, and inefficient because it damages all tracks. Use more specific modify_* functions, if possible.

update_all :: M m => m () Source #

Emit track updates for all tracks. Use this when events have changed but I don't know which ones, e.g. when loading a file or restoring a previous state.

errors

data Error Source #

Abort is used by Cmd, so don't throw it from here. This isn't exactly modular, but ErrorT can't be composed and extensible exceptions are too much bother at the moment.

Constructors

Error !CallStack !Text 
Abort 

Instances

Instances details
Show Error Source # 
Instance details

Defined in Ui.Ui

Methods

showsPrec :: Int -> Error -> ShowS #

show :: Error -> String #

showList :: [Error] -> ShowS #

Pretty.Pretty Error Source # 
Instance details

Defined in Ui.Ui

Monad m => MonadError Error (CmdT m) Source # 
Instance details

Defined in Cmd.Cmd

Methods

throwError :: Error -> CmdT m a #

catchError :: CmdT m a -> (Error -> CmdT m a) -> CmdT m a #

Monad m => MonadError Error (StateT m) Source # 
Instance details

Defined in Ui.Ui

Methods

throwError :: Error -> StateT m a #

catchError :: StateT m a -> (Error -> StateT m a) -> StateT m a #

require_right :: (CallStack.Stack, M m) => (err -> Text) -> Either err a -> m a Source #

config

get_default :: M m => (UiConfig.Default -> a) -> m a Source #

set_root_id :: M m => Id.BlockId -> m () Source #

modify_config :: M m => (UiConfig.Config -> UiConfig.Config) -> m () Source #

Unlike other State fields, you can modify Config freely without worrying about breaking invariants. TODO except allocations have invariants.

get_config :: M m => (UiConfig.Config -> a) -> m a Source #

with_config :: M m => (UiConfig.Config -> UiConfig.Config) -> m a -> m a Source #

Run the action with a modified state, and restore it.

allocation :: ScoreT.Instrument -> Lens.Lens State (Maybe.Maybe UiConfig.Allocation) Source #

TODO use this for read only. If used for write it bypasses UiConfig.allocate.

view

all_view_ids :: M m => m [Id.ViewId] Source #

All ViewIds, in sorted order.

create_view :: M m => Id.Id -> Block.View -> m Id.ViewId Source #

Create a new view. Block.view_tracks can be left empty, since it will be replaced by views generated from the the block. If the caller uses the Block.view constructor, it won't have to worry about this.

Throw if the ViewId already exists.

destroy_view :: M m => Id.ViewId -> m () Source #

set_view_status :: M m => Id.ViewId -> (Int, Text) -> Maybe.Maybe Text -> m () Source #

Set a status variable on a view.

zoom and track scroll

set_view_padding :: M m => Id.ViewId -> Block.Padding -> m () Source #

Only ui_update is supposed to call this, because the UI is responsible for the padding.

selections

get_selection :: M m => Id.ViewId -> Sel.Num -> m (Maybe.Maybe Sel.Selection) Source #

Get view_id's selection at selnum, or Nothing if there is none.

set_selection :: M m => Id.ViewId -> Sel.Num -> Maybe.Maybe Sel.Selection -> m () Source #

Replace any selection on view_id at selnum with sel.

shift_selection Source #

Arguments

:: Bool

skip unselectable tracks

-> Block.Block 
-> Types.TrackNum 
-> Sel.Selection 
-> Sel.Selection 

Shift the selection, clipping if it's out of range. While the sel_cur_track won't be on a non-selectable track after this, the selection may still include one.

skip_unselectable_tracks :: Block.Block -> Types.TrackNum -> Int -> Types.TrackNum Source #

Shift a tracknum to another track, skipping unselectable tracks.

selectable_tracks :: Block.Block -> [Types.TrackNum] Source #

Get the tracknums from a block that should be selectable.

block

all_block_track_ids :: M m => m [(Id.BlockId, [Id.TrackId])] Source #

Get all blocks along with their tracks.

create_config_block :: M m => Id.Id -> Block.Block -> m Id.BlockId Source #

Make a new block. If it's the first one, it will be set as the root. This is the low level version, you probably want to use create_block.

Throw if the BlockId already exists.

create_block :: M m => Id.Id -> Text -> [Block.Track] -> m Id.BlockId Source #

Make a new block with the default Block.Config.

destroy_block :: M m => Id.BlockId -> m () Source #

Destroy the block and all the views that display it. If the block was the root, it will be be unset. The block's tracks are left intact.

views_of :: M m => Id.BlockId -> m (Map Id.ViewId Block.View) Source #

Get all views of a given block.

set_integrated_block :: M m => Id.BlockId -> Maybe.Maybe (Id.BlockId, Block.TrackDestinations) -> m () Source #

Set or clear this block as an integrate destination. The automatic integration system will update it from the given source block.

set_integrated_manual :: M m => Id.BlockId -> Block.SourceKey -> Maybe.Maybe [Block.NoteDestination] -> m () Source #

Set or clear the block's manual integration Block.NoteDestinations. This just attaches (or removes) the integrate information to the block so a future integration can use it to merge, and then call this function again.

set_play_box :: M m => Id.BlockId -> Color.Color -> m () Source #

The play box doesn't use a char, so I leave that out.

block_ruler_end :: M m => Id.BlockId -> m TrackTime Source #

Get the end of the block according to the ruler. This means that if the block has no rulers (e.g. a clipboard block) then block_ruler_end will be 0.

block_event_end :: M m => Id.BlockId -> m TrackTime Source #

Get the end of the block according to the last event of the block.

block_end :: M m => Id.BlockId -> m TrackTime Source #

Get the maximum of ruler end and event end. The end may still be 0 if the block is totally empty.

block_logical_range :: M m => Id.BlockId -> m (TrackTime, TrackTime) Source #

The logical range is defined by Ruler.bounds_of and is intended to correspond to the "note" that this block defines.

skeleton

toggle_skeleton_edge Source #

Arguments

:: M m 
=> Bool

If not true, the child's existing parents will be unlinked. While a track with multiple parents is possible, and is a way to express the same score derived under different conditions, in practice I never do that.

-> Id.BlockId 
-> Skeleton.Edge 
-> m Bool 

Toggle the given edge in the block's skeleton. If a cycle would be created, refuse to add the edge and return False. The edge is in (parent, child) order.

add_edges :: M m => Id.BlockId -> [Skeleton.Edge] -> m () Source #

Add the edges to the skeleton. Throw if they would produce a cycle.

splice_skeleton_above :: M m => Id.BlockId -> Types.TrackNum -> Types.TrackNum -> m () Source #

The first tracknum is spliced above the second.

splice_skeleton_below :: M m => Id.BlockId -> Types.TrackNum -> Types.TrackNum -> m () Source #

The first tracknum is spliced below the second.

tracks

insert_track :: M m => Id.BlockId -> Types.TrackNum -> Block.Track -> m () Source #

Insert a track at the given TrackNum. The TrackNum can be out of range to insert a track at the beginning or append it to the end.

This will throw if it's an event track and the block already contains that TrackId. This invariant ensures that a (BlockId, TrackNum) is interchangeable with a TrackId.

remove_track :: M m => Id.BlockId -> Types.TrackNum -> m () Source #

Remove the track at the given tracknum.

move_track :: M m => Id.BlockId -> Types.TrackNum -> Types.TrackNum -> m () Source #

Move a track from one tracknum to another.

tracks by tracknum

track_count :: M m => Id.BlockId -> m Types.TrackNum Source #

Number of tracks in the block. This includes the ruler, so subtract 1 if you want all non-ruler tracks.

block_track_at :: M m => Id.BlockId -> Types.TrackNum -> m (Maybe.Maybe Block.Track) Source #

Get the Track at tracknum, or Nothing if its out of range.

event_track_at :: M m => Id.BlockId -> Types.TrackNum -> m (Maybe.Maybe Id.TrackId) Source #

Like track_at, but only for event tracks.

get_event_track_at :: M m => Id.BlockId -> Types.TrackNum -> m Id.TrackId Source #

Like event_track_at but throws if it's not there or not an event track.

ruler_track_at :: M m => Id.BlockId -> Types.TrackNum -> m (Maybe.Maybe Id.RulerId) Source #

Get the RulerId of an event or ruler track, or Nothing if the tracknum is out of range or doesn't have a ruler.

block_ruler :: M m => Id.BlockId -> m Id.RulerId Source #

0 is the conventional ruler tracknum.

tracks by TrackId

track_ids_of :: M m => Id.BlockId -> m [Id.TrackId] Source #

Get all TrackIds of the given block.

tracknums_of :: M m => Id.BlockId -> m [(Id.TrackId, Types.TrackNum)] Source #

Get all TrackIds of the given block, along with their tracknums.

block_tracknums :: M m => Id.BlockId -> m [(Block.Track, Types.TrackNum)] Source #

Get tracks along with their TrackNums.

tracknum_of :: M m => Id.BlockId -> Id.TrackId -> m (Maybe.Maybe Types.TrackNum) Source #

There can only be one TrackId per block, which allows TrackNums and TrackIds to be interchangeable. This is enforced by insert_track.

The inverse is event_track_at.

block track

merge_track :: M m => Id.BlockId -> Types.TrackNum -> Types.TrackNum -> m () Source #

Merge the from tracknum into the to tracknum and collapse from.

unmerge_track :: M m => Id.BlockId -> Types.TrackNum -> m () Source #

Reverse merge_track: remove the merged tracks and expand their occurrances in the given block. "Unmerge" is not a graceful term, but at least it's obviously the opposite of "merge".

set_ruler_ids :: M m => Id.BlockId -> [Maybe.Maybe Id.RulerId] -> m () Source #

Set rulers, one per track.

replace_ruler_id :: M m => Id.BlockId -> Id.RulerId -> Id.RulerId -> m () Source #

Replace one RulerId with another on the given block.

It's more convenient to do here than removing and inserting tracks, and easy since there's no "one per block" invariant to maintain with ruler ids.

get_tracklike :: M m => Block.TracklikeId -> m Block.Tracklike Source #

Resolve a TracklikeId to a Tracklike.

track

create_track :: M m => Id.Id -> Track.Track -> m Id.TrackId Source #

Insert the given track with the given ID.

Throw if the TrackId already exists.

destroy_track :: M m => Id.TrackId -> m () Source #

Destroy the track and remove it from all the blocks it's in. No-op if the TrackId doesn't exist.

modify_track_title :: M m => Id.TrackId -> (Text -> Text) -> m () Source #

modify_waveform :: M m => Id.TrackId -> (Bool -> Bool) -> m () Source #

blocks_with_track_id :: M m => Id.TrackId -> m [(Id.BlockId, [(Types.TrackNum, Block.TracklikeId)])] Source #

Find track_id in all the blocks it exists in, and return the track info for each tracknum at which track_id lives. Blocks with no matching tracks won't be returned, so the return track lists will always be non-null.

events

insert_events :: M m => Id.TrackId -> [Event.Event] -> m () Source #

Insert events into track_id as per Events.insert.

insert_block_events :: M m => Id.BlockId -> Id.TrackId -> [Event.Event] -> m () Source #

Like insert_events, but clip the events to the end of a block.

This is necessarily block specific, because block duration is defined by its ruler. Still, you should use this in preference to insert_events.

This uses block_end, which means that if events don't already go past the end of the ruler, they won't after this is called. If they are already past (e.g. there is no ruler), then they will only be clipped if they move to later in time. This might be confusing, but it seems generally convenient to not have to constantly manually trim events when they get moved past the end of the ruler, but definitely inconvenient for events to just disappear when there is no ruler.

modify_events :: M m => Id.TrackId -> (Events.Events -> Events.Events) -> m () Source #

Modify the events on a track, and assume the entire track has been damaged.

modify_some_events :: M m => Id.TrackId -> (Events.Events -> Events.Events) -> m () Source #

Just like modify_events, except that it expects you only modified a few events, and will only emit damage for the changed parts.

remove_event :: M m => Id.TrackId -> Event.Event -> m () Source #

Remove a single event by start and orientation. TODO I think remove_events_range is now just as expressive and can be just as efficient

remove_events :: M m => Id.TrackId -> [Event.Event] -> m () Source #

Just like mapM_ (remove_event track_id) but more efficient. TODO at least I hope, it got sort of complicated.

track_event_end :: M m => Id.TrackId -> m TrackTime Source #

Get the end of the last event of the block.

ruler

create_ruler :: M m => Id.Id -> Ruler.Ruler -> m Id.RulerId Source #

Insert the given ruler with the given ID.

Throw if the RulerId already exists.

destroy_ruler :: M m => Id.RulerId -> m () Source #

Destroy the ruler and remove it from all the blocks it's in.

no_ruler :: Id.RulerId Source #

Since all TracklikeIds must have a ruler, all States have a special empty ruler that can be used in a "no ruler" situation.

This RulerId is implicitly present in every block. It's not actually in state_rulers to avoid it getting renamed or deleted, but get_ruler will pretend it exists. As long as everyone that cares about no_ruler (which is only verify and get_tracklike for Ui.Sync) uses get_ruler then they won't be confused by tracks that have no_ruler.

util

verify

quick_verify :: Update.UiDamage -> State -> Either String (State, [Text]) Source #

This is like verify, but less complete. It returns Left if it wants you to reject the new state entirely.

verify is better, but more expensive, so I'm reluctant to run it on every single cmd. If I run verify before unsafe puts and trust this module to maintain invariants then I don't need to, but I don't fully trust this module.

TODO a better approach would be to make sure Sync can't be broken by State.

verify :: State -> (State, [Text]) Source #

Unfortunately there are some invariants to protect within State. They can all be fixed by dropping things, so this will fix them and return a list of warnings.

fix_state :: M m => m [Text] Source #

ID

read_id :: (CallStack.Stack, Id.Ident a, M m) => Text -> m a Source #

Read an ID of the form "namespace/name", or just "name", filling in the current namespace if it's not present.