Karya, built on Mon Jul 24 11:39:07 PDT 2017 (patch 33511aca01257b76b88de7c7a2763b7a965c084e)

Safe HaskellNone

Ui.Ui

Contents

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

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.

config

data Config Source #

Miscellaneous config data.

Constructors

Config 

Fields

type SavedViews = Map Text (Map Id.ViewId Block.View, Maybe.Maybe Id.ViewId) Source #

This is a place to save sets of views so you can switch between them. The ViewId is the one with focus.

data Meta Source #

Extra data that doesn't have any effect on the score.

Constructors

Meta 

Fields

data Performance a Source #

A record of the last successful performance that sounded as expected. You can compare this with the current performance to see if code changes have messed things up.

I'm ambivalent about including this in the save file, since it will be saved and loaded all the time when it should rarely change. But it seems like the only reliable way to keep the score and performance in sync. Besides, it shouldn't actually be that large, and if it is, the git repo save should only save it when Config changes. I could also split it into its own file.

Constructors

Performance 

Fields

  • perf_performance :: !a
     
  • perf_creation :: !UTCTime

    The time this performance was recorded.

  • perf_patch :: !Text

    The sequencer's patch level. For darcs, this should be a patch name (technically it should be a tag's name, but it doesn't matter as long as I'm the only developer). For git, it would be the commit hash.

data Default Source #

Initial values for derivation.

This used to have other fields, but they were replaced by the more general ky and the implicit GLOBAL call. I haven't removed tempo yet because it's the only way to change the speed for tempo-less blocks, and doesn't affect (or rather, is undone automatically) for integrated blocks.

Constructors

Default 

Fields

  • default_tempo :: !Y

    A toplevel block without a tempo track will get this tempo.

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

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

StateT monad

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

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

Minimal complete definition

get, unsafe_put, update, get_updates, throw_error

Methods

get :: m State Source #

unsafe_put :: State -> m () Source #

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

update :: Update.CmdUpdate -> m () Source #

get_updates :: m [Update.CmdUpdate] Source #

throw_error :: Error -> m a Source #

data StateT m a Source #

Instances

MonadTrans StateT # 

Methods

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

Monad m => MonadError Error (StateT m) # 

Methods

throwError :: Error -> StateT m a #

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

Monad m => Monad (StateT m) # 

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 #

fail :: String -> StateT m a #

Functor m => Functor (StateT m) # 

Methods

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

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

Monad m => Applicative.Applicative (StateT m) # 

Methods

pure :: a -> StateT m a #

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

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

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

MonadIO m => MonadIO (StateT m) # 

Methods

liftIO :: IO a -> StateT m a #

(Applicative.Applicative m, Monad m) => M (StateT m) # 

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.

update :: M m => Update.CmdUpdate -> 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.CmdUpdate])) 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.

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

Show Error # 

Methods

showsPrec :: Int -> Error -> ShowS #

show :: Error -> String #

showList :: [Error] -> ShowS #

Pretty.Pretty Error # 
Monad m => MonadError Error (StateT m) # 

Methods

throwError :: Error -> StateT m a #

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

Monad m => MonadError Error (CmdT m) # 

Methods

throwError :: Error -> CmdT m a #

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

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

config

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

modify_default :: M m => (Default -> Default) -> m () Source #

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

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

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

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

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

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

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

TODO use this for read only. If used for write it bypasses 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_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.

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 #

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 go past the end of the ruler, they won't. 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_event_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.

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

Remove any events whose starting positions fall within a range.

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

Remove from the pont to the end.

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 :: 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.

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.