2019-11-08 04:58:13 +00:00
/*
4 coder_tutorial . cpp - Guided graphical tutorial system .
*/
// TOP
2019-11-09 19:55:05 +00:00
global Tutorial_State tutorial = { } ;
2019-11-08 04:58:13 +00:00
2019-11-09 01:33:35 +00:00
CUSTOM_COMMAND_SIG ( kill_tutorial )
2019-11-09 19:55:05 +00:00
CUSTOM_DOC ( " If there is an active tutorial, kill it. " )
2019-11-09 01:33:35 +00:00
{
2019-11-09 19:55:05 +00:00
if ( ! tutorial . in_tutorial ) {
2019-11-08 06:38:51 +00:00
return ;
}
2019-11-09 19:55:05 +00:00
tutorial . in_tutorial = false ;
view_close ( app , tutorial . view ) ;
2019-11-08 06:38:51 +00:00
}
function void
tutorial_activate ( Application_Links * app ) {
2019-11-09 19:55:05 +00:00
if ( ! tutorial . in_tutorial ) {
2019-11-08 06:38:51 +00:00
return ;
}
2019-11-09 19:55:05 +00:00
Panel_ID panel = view_get_panel ( app , tutorial . view ) ;
2019-11-08 06:38:51 +00:00
Panel_ID parent = panel_get_parent ( app , panel ) ;
panel_set_split ( app , parent , PanelSplitKind_Ratio_Min , 0.5f ) ;
2019-11-09 19:55:05 +00:00
tutorial . is_active = true ;
2019-11-08 06:38:51 +00:00
}
function void
tutorial_deactivate ( Application_Links * app ) {
2019-11-09 19:55:05 +00:00
if ( ! tutorial . in_tutorial ) {
2019-11-08 06:38:51 +00:00
return ;
2019-11-08 04:58:13 +00:00
}
2019-11-08 06:38:51 +00:00
Face_ID face = get_face_id ( app , 0 ) ;
Face_Metrics metrics = get_face_metrics ( app , face ) ;
f32 line_height = metrics . line_height ;
2019-11-09 19:55:05 +00:00
Panel_ID panel = view_get_panel ( app , tutorial . view ) ;
2019-11-08 06:38:51 +00:00
Panel_ID parent = panel_get_parent ( app , panel ) ;
panel_set_split ( app , parent , PanelSplitKind_FixedPixels_Min , line_height * 4.f ) ;
2019-11-09 19:55:05 +00:00
tutorial . is_active = false ;
2019-11-08 04:58:13 +00:00
}
2019-11-09 01:33:35 +00:00
function void
tutorial_action ( Application_Links * app , Tutorial_Action action ) {
2019-11-09 19:55:05 +00:00
switch ( action ) {
case TutorialAction_Prev :
{
tutorial . slide_index - = 1 ;
} break ;
case TutorialAction_Next :
{
tutorial . slide_index + = 1 ;
} break ;
case TutorialAction_Exit :
{
kill_tutorial ( app ) ;
} break ;
case TutorialAction_Restart :
{
tutorial . slide_index = 0 ;
} break ;
}
2019-11-09 01:33:35 +00:00
}
2019-11-08 04:58:13 +00:00
function void
2019-11-09 19:55:05 +00:00
tutorial_init_title_face ( Application_Links * app ) {
if ( tutorial . face = = 0 ) {
Face_ID face = get_face_id ( app , 0 ) ;
Face_Description face_description = get_face_description ( app , face ) ;
face_description . parameters . pt_size * = 2 ;
tutorial . face = try_create_new_face ( app , & face_description ) ;
if ( tutorial . face = = 0 ) {
tutorial . face = face ;
}
}
}
function void
default_4coder_tutorial_render ( Application_Links * app , Frame_Info frame_info , View_ID view_id ) {
Face_ID face = get_face_id ( app , 0 ) ;
tutorial_init_title_face ( app ) ;
Face_Metrics metrics = get_face_metrics ( app , face ) ;
Face_Metrics tut_metrics = get_face_metrics ( app , tutorial . face ) ;
////////
Scratch_Block scratch ( app ) ;
tutorial . slide_index = clamp ( 0 , tutorial . slide_index , tutorial . slide_count - 1 ) ;
Tutorial_Slide_Function * slide_func = tutorial . slide_func_ptrs [ tutorial . slide_index ] ;
Tutorial_Slide slide = slide_func ( app , scratch ) ;
////////
f32 h0 = get_fancy_line_height ( app , 0 , & slide . short_details ) ;
f32 h1 = get_fancy_line_height ( app , 0 , slide . long_details . first ) ;
f32 title_height = max ( h0 , h1 ) ;
////////
2019-11-08 04:58:13 +00:00
View_ID active_view = get_active_view ( app , Access_Always ) ;
b32 is_active_view = ( active_view = = view_id ) ;
2019-11-08 06:38:51 +00:00
FColor margin_color = get_margin_color ( is_active_view ? UIHighlight_Active : UIHighlight_None ) ;
Rect_f32 region = draw_background_and_margin ( app , view_id , margin_color , margin_color ) ;
2019-11-08 04:58:13 +00:00
Rect_f32 prev_clip = draw_set_clip ( app , region ) ;
2019-11-09 01:33:35 +00:00
f32 panel_y0 = region . y0 - 3.f ;
2019-11-08 06:38:51 +00:00
region = rect_inner ( region , 3.f ) ;
draw_rectangle ( app , region , 20.f , fcolor_id ( Stag_Back ) ) ;
2019-11-09 01:33:35 +00:00
region = rect_inner ( region , 10.f ) ;
2019-11-09 19:55:05 +00:00
Vec2_f32 title_p = V2f32 ( region . x0 , panel_y0 + ( metrics . line_height * 2.f ) - title_height * 0.5f ) ;
2019-11-09 01:33:35 +00:00
2019-11-08 06:38:51 +00:00
if ( is_active_view ) {
2019-11-09 19:55:05 +00:00
if ( ! tutorial . is_active ) {
2019-11-08 06:38:51 +00:00
view_enqueue_command_function ( app , view_id , tutorial_activate ) ;
}
}
else {
2019-11-09 19:55:05 +00:00
if ( tutorial . is_active ) {
2019-11-08 06:38:51 +00:00
view_enqueue_command_function ( app , view_id , tutorial_deactivate ) ;
}
}
2019-11-09 19:55:05 +00:00
tutorial . hover_action = TutorialAction_None ;
if ( tutorial . is_active ) {
draw_fancy_block ( app , 0 , fcolor_zero ( ) , & slide . long_details , title_p ) ;
2019-11-09 01:33:35 +00:00
// NOTE(allen): buttons
2019-11-09 19:55:05 +00:00
Rect_f32_Pair footer_pair = rect_split_top_bottom_neg ( region , metrics . line_height * 2.f ) ;
Rect_f32 footer = footer_pair . max ;
2019-11-09 01:33:35 +00:00
footer . x0 + = 10.f ;
footer . y0 - = 10.f ;
footer . y1 - = 10.f ;
f32 b_width = metrics . normal_advance * 10.f ;
Mouse_State mouse = get_mouse_state ( app ) ;
Vec2_f32 m_p = V2f32 ( mouse . p ) ;
2019-11-09 19:55:05 +00:00
{
Rect_f32_Pair pair = rect_split_left_right ( footer , b_width ) ;
footer = pair . max ;
footer . x0 + = 10.f ;
if ( tutorial . slide_index > 0 ) {
if ( draw_button ( app , pair . min , m_p , face , string_u8_litexpr ( " prev " ) ) ) {
tutorial . hover_action = TutorialAction_Prev ;
}
}
}
{
Rect_f32_Pair pair = rect_split_left_right ( footer , b_width ) ;
footer = pair . max ;
footer . x0 + = 10.f ;
if ( tutorial . slide_index < tutorial . slide_count - 1 ) {
if ( draw_button ( app , pair . min , m_p , face , string_u8_litexpr ( " next " ) ) ) {
tutorial . hover_action = TutorialAction_Next ;
}
2019-11-09 01:33:35 +00:00
}
}
2019-11-09 19:55:05 +00:00
{
Rect_f32_Pair pair = rect_split_left_right ( footer , b_width ) ;
footer = pair . max ;
footer . x0 + = 10.f ;
Rect_f32 exit_box = pair . min ;
pair = rect_split_left_right ( footer , b_width ) ;
Rect_f32 restart_box = pair . min ;
if ( tutorial . slide_index = = tutorial . slide_count - 1 ) {
if ( draw_button ( app , exit_box , m_p , face , string_u8_litexpr ( " exit " ) ) ) {
tutorial . hover_action = TutorialAction_Exit ;
}
if ( draw_button ( app , restart_box , m_p , face , string_u8_litexpr ( " restart " ) ) ) {
tutorial . hover_action = TutorialAction_Restart ;
}
}
}
2019-11-09 01:33:35 +00:00
}
else {
2019-11-09 19:55:05 +00:00
draw_fancy_line ( app , 0 , fcolor_zero ( ) , & slide . short_details , title_p ) ;
2019-11-09 01:33:35 +00:00
}
2019-11-08 04:58:13 +00:00
draw_set_clip ( app , prev_clip ) ;
}
function void
2019-11-09 19:55:05 +00:00
default_4coder_tutorial_run ( Application_Links * app )
2019-11-08 04:58:13 +00:00
{
View_ID view = get_this_ctx_view ( app , Access_Always ) ;
View_Context ctx = view_current_context ( app , view ) ;
2019-11-09 19:55:05 +00:00
ctx . render_caller = default_4coder_tutorial_render ;
2019-11-08 04:58:13 +00:00
ctx . hides_buffer = true ;
View_Context_Block ctx_block ( app , view , & ctx ) ;
2019-11-09 19:55:05 +00:00
tutorial . in_tutorial = true ;
tutorial . view = view ;
2019-11-08 06:38:51 +00:00
2019-11-08 04:58:13 +00:00
for ( ; ; ) {
2019-11-08 06:38:51 +00:00
User_Input in = get_next_input ( app , EventPropertyGroup_Any , 0 ) ;
if ( in . abort ) {
2019-11-08 04:58:13 +00:00
break ;
}
2019-11-08 06:38:51 +00:00
b32 handled = true ;
switch ( in . event . kind ) {
2019-11-09 01:33:35 +00:00
case InputEventKind_MouseButton :
{
if ( in . event . mouse . code = = MouseCode_Left ) {
2019-11-09 19:55:05 +00:00
tutorial . depressed_action = tutorial . hover_action ;
2019-11-09 01:33:35 +00:00
}
} break ;
case InputEventKind_MouseButtonRelease :
{
if ( in . event . mouse . code = = MouseCode_Left ) {
2019-11-09 19:55:05 +00:00
if ( tutorial . depressed_action = = tutorial . hover_action ) {
tutorial_action ( app , tutorial . depressed_action ) ;
2019-11-09 01:33:35 +00:00
}
}
} break ;
2019-11-08 06:38:51 +00:00
case InputEventKind_Core :
{
switch ( in . event . core . code ) {
case CoreCode_ClickActivateView :
{
tutorial_activate ( app ) ;
} break ;
case CoreCode_ClickDeactivateView :
{
tutorial_deactivate ( app ) ;
} break ;
default :
{
handled = false ;
} break ;
}
} break ;
default :
{
handled = false ;
} break ;
}
if ( ! handled ) {
Mapping * mapping = ctx . mapping ;
Command_Map * map = mapping_get_map ( mapping , ctx . map_id ) ;
Fallback_Dispatch_Result disp_result =
fallback_command_dispatch ( app , mapping , map , & in ) ;
if ( disp_result . code = = FallbackDispatch_DelayedUICall ) {
call_after_ctx_shutdown ( app , view , disp_result . func ) ;
break ;
}
if ( disp_result . code = = FallbackDispatch_Unhandled ) {
leave_current_input_unhandled ( app ) ;
}
}
2019-11-08 04:58:13 +00:00
}
2019-11-08 06:38:51 +00:00
2019-11-09 19:55:05 +00:00
tutorial . in_tutorial = false ;
}
global String_Const_u8 hms_title = string_u8_litexpr ( " Handmade Seattle Demo " ) ;
function void
default_4coder_tutorial_short_details ( Application_Links * app , Arena * arena , Fancy_Line * short_details ) {
Face_ID face = get_face_id ( app , 0 ) ;
push_fancy_string ( arena , short_details , tutorial . face , fcolor_id ( Stag_Pop1 ) , hms_title ) ;
push_fancy_string ( arena , short_details , face , fcolor_id ( Stag_Default ) , 8.f , 8.f , string_u8_litexpr ( " Welcome to Handmade Seattle and to this 4coder demo! " ) ) ;
push_fancy_string ( arena , short_details , face , fcolor_id ( Stag_Pop2 ) , string_u8_litexpr ( " Click here to see more. " ) ) ;
}
function void
default_4coder_tutorial_long_start ( Application_Links * app , Arena * arena , Fancy_Block * long_details ) {
Fancy_Line * line = push_fancy_line ( arena , long_details , tutorial . face , fcolor_id ( Stag_Pop1 ) , hms_title ) ;
Face_ID face = get_face_id ( app , 0 ) ;
//
line = push_fancy_line ( arena , long_details , face , fcolor_id ( Stag_Default ) ) ;
# define M "If there are any features you'd like to know about or try out, "
push_fancy_string ( arena , line , string_u8_litexpr ( M ) ) ;
# undef M
push_fancy_string ( arena , line , fcolor_id ( Stag_Keyword ) , string_u8_litexpr ( " ask! " ) ) ;
//
push_fancy_line ( arena , long_details , face , fcolor_id ( Stag_Pop1 ) , string_u8_litexpr ( " " ) ) ;
}
function Tutorial_Slide
default_4coder_tutorial_slide_1 ( Application_Links * app , Arena * arena ) {
Tutorial_Slide result = { } ;
Face_ID face = get_face_id ( app , 0 ) ;
tutorial_init_title_face ( app ) ;
default_4coder_tutorial_short_details ( app , arena , & result . short_details ) ;
Fancy_Block * long_details = & result . long_details ;
default_4coder_tutorial_long_start ( app , arena , long_details ) ;
Fancy_Line * line = 0 ;
//
push_fancy_line ( arena , long_details , face , fcolor_id ( Stag_Keyword ) , string_u8_litexpr ( " Let's start with a few navigation commands: " ) ) ;
//
line = push_fancy_line ( arena , long_details , face , fcolor_id ( Stag_Default ) ) ;
push_fancy_stringf ( arena , line , fcolor_id ( Stag_Pop2 ) ,
" %40s " , " <Control Comma> " ) ;
push_fancy_stringf ( arena , line , " change active panel " ) ;
//
line = push_fancy_line ( arena , long_details , face , fcolor_id ( Stag_Default ) ) ;
push_fancy_stringf ( arena , line , fcolor_id ( Stag_Pop2 ) ,
" %40s " , " <*AnyArrow*> " ) ;
push_fancy_stringf ( arena , line , " move cursor one character or line " ) ;
//
line = push_fancy_line ( arena , long_details , face , fcolor_id ( Stag_Default ) ) ;
push_fancy_stringf ( arena , line , fcolor_id ( Stag_Pop2 ) ,
" %40s " , " <Control *AnyArrow*> " ) ;
push_fancy_stringf ( arena , line , " move cursor by 'chunks' " ) ;
//
line = push_fancy_line ( arena , long_details , face , fcolor_id ( Stag_Default ) ) ;
push_fancy_stringf ( arena , line , fcolor_id ( Stag_Pop2 ) ,
" %40s " , " <Home>, <End> " ) ;
push_fancy_stringf ( arena , line , " move cursor to the first/last character of the line " ) ;
//
line = push_fancy_line ( arena , long_details , face , fcolor_id ( Stag_Default ) ) ;
push_fancy_stringf ( arena , line , fcolor_id ( Stag_Pop2 ) ,
" %40s " , " <PageUp>, <PageDown> " ) ;
push_fancy_stringf ( arena , line , " move cursor by full pages up/down " ) ;
//
push_fancy_line ( arena , long_details , face , fcolor_id ( Stag_Keyword ) , string_u8_litexpr ( " Available in code files: " ) ) ;
//
line = push_fancy_line ( arena , long_details , face , fcolor_id ( Stag_Default ) ) ;
push_fancy_stringf ( arena , line , fcolor_id ( Stag_Pop2 ) ,
" %40s " , " <Alt OpenBracket> " ) ;
push_fancy_stringf ( arena , line , " move cursor and mark to surrounding scope " ) ;
//
line = push_fancy_line ( arena , long_details , face , fcolor_id ( Stag_Default ) ) ;
push_fancy_stringf ( arena , line , fcolor_id ( Stag_Pop2 ) ,
" %40s " , " <Alt CloseBracket> " ) ;
push_fancy_stringf ( arena , line , " move cursor and mark to previous scope " ) ;
//
line = push_fancy_line ( arena , long_details , face , fcolor_id ( Stag_Default ) ) ;
push_fancy_stringf ( arena , line , fcolor_id ( Stag_Pop2 ) ,
" %40s " , " <Alt SingleQuote> " ) ;
push_fancy_stringf ( arena , line , " move cursor and mark to next scope " ) ;
//
line = push_fancy_line ( arena , long_details , face , fcolor_id ( Stag_Default ) ) ;
push_fancy_stringf ( arena , line , fcolor_id ( Stag_Pop2 ) ,
" %40s " , " <Alt Shift CloseBracket> " ) ;
push_fancy_stringf ( arena , line , " move cursor and mark to previous top-level scope " ) ;
//
line = push_fancy_line ( arena , long_details , face , fcolor_id ( Stag_Default ) ) ;
push_fancy_stringf ( arena , line , fcolor_id ( Stag_Pop2 ) ,
" %40s " , " <Alt Shift SingleQuote> " ) ;
push_fancy_stringf ( arena , line , " move cursor and mark to next top-level scope " ) ;
return ( result ) ;
}
function Tutorial_Slide
default_4coder_tutorial_slide_2 ( Application_Links * app , Arena * arena ) {
Tutorial_Slide result = { } ;
Face_ID face = get_face_id ( app , 0 ) ;
tutorial_init_title_face ( app ) ;
default_4coder_tutorial_short_details ( app , arena , & result . short_details ) ;
Fancy_Block * long_details = & result . long_details ;
default_4coder_tutorial_long_start ( app , arena , long_details ) ;
Fancy_Line * line = 0 ;
//
push_fancy_line ( arena , long_details , face , fcolor_id ( Stag_Keyword ) , string_u8_litexpr ( " 4coder's default editing paradigm is emacs-like but with some differences: " ) ) ;
//
line = push_fancy_line ( arena , long_details , face , fcolor_id ( Stag_Default ) ) ;
push_fancy_stringf ( arena , line , fcolor_id ( Stag_Pop2 ) ,
" %40s " , " <TextInsert> " ) ;
push_fancy_stringf ( arena , line , " non-modal text insertion works in any user-writable buffers at the cursor " ) ;
//
line = push_fancy_line ( arena , long_details , face , fcolor_id ( Stag_Default ) ) ;
push_fancy_stringf ( arena , line , fcolor_id ( Stag_Pop2 ) ,
" %40s " , " <Backspace>/<Delete> " ) ;
push_fancy_stringf ( arena , line , " delete the previous/next character from the cursor " ) ;
//
line = push_fancy_line ( arena , long_details , face , fcolor_id ( Stag_Default ) ) ;
push_fancy_stringf ( arena , line , fcolor_id ( Stag_Pop2 ) ,
" %40s " , " <Control Space> " ) ;
push_fancy_stringf ( arena , line , " moves the mark to the cursor cursor " ) ;
//
line = push_fancy_line ( arena , long_details , face , fcolor_id ( Stag_Default ) ) ;
push_fancy_stringf ( arena , line , fcolor_id ( Stag_Pop2 ) ,
" %40s " , " <Control D> " ) ;
push_fancy_stringf ( arena , line , " delete the cursor to mark range " ) ;
//
line = push_fancy_line ( arena , long_details , face , fcolor_id ( Stag_Default ) ) ;
push_fancy_stringf ( arena , line , fcolor_id ( Stag_Pop2 ) ,
" %40s " , " <Control C> " ) ;
push_fancy_stringf ( arena , line , " copy the cursor to mark range to the clipboard " ) ;
//
line = push_fancy_line ( arena , long_details , face , fcolor_id ( Stag_Default ) ) ;
push_fancy_stringf ( arena , line , fcolor_id ( Stag_Pop2 ) ,
" %40s " , " <Control X> " ) ;
push_fancy_stringf ( arena , line , " cut the cursor to mark range to the clipboard " ) ;
//
line = push_fancy_line ( arena , long_details , face , fcolor_id ( Stag_Default ) ) ;
push_fancy_stringf ( arena , line , fcolor_id ( Stag_Pop2 ) ,
" %40s " , " <Control V> " ) ;
push_fancy_stringf ( arena , line , " paste the clipboard to the buffer " ) ;
//
line = push_fancy_line ( arena , long_details , face , fcolor_id ( Stag_Default ) ) ;
push_fancy_stringf ( arena , line , fcolor_id ( Stag_Pop2 ) ,
" %40s " , " <Control Shift V> " ) ;
push_fancy_stringf ( arena , line , " paste the clipboard to the buffer cycling through the clipboard's 'clips' " ) ;
//
line = push_fancy_line ( arena , long_details , face , fcolor_id ( Stag_Default ) ) ;
push_fancy_stringf ( arena , line , fcolor_id ( Stag_Pop2 ) ,
" %40s " , " <Control Z> " ) ;
push_fancy_stringf ( arena , line , " undo the last edit " ) ;
//
line = push_fancy_line ( arena , long_details , face , fcolor_id ( Stag_Default ) ) ;
push_fancy_stringf ( arena , line , fcolor_id ( Stag_Pop2 ) ,
" %40s " , " <Control Y> " ) ;
push_fancy_stringf ( arena , line , " redo the last undone edit " ) ;
return ( result ) ;
2019-11-08 04:58:13 +00:00
}
2019-11-09 01:33:35 +00:00
CUSTOM_COMMAND_SIG ( default_4coder_tutorial )
2019-11-08 04:58:13 +00:00
CUSTOM_DOC ( " Tutorial for built in 4coder bindings and features. " )
{
kill_tutorial ( app ) ;
Panel_ID root_panel = panel_get_root ( app ) ;
if ( panel_split ( app , root_panel , Dimension_Y ) ) {
panel_swap_children ( app , root_panel ) ;
Panel_ID tutorial_panel = panel_get_child ( app , root_panel , Side_Min ) ;
2019-11-09 19:55:05 +00:00
tutorial . view = panel_get_view ( app , tutorial_panel , Access_Always ) ;
view_set_passive ( app , tutorial . view , true ) ;
2019-11-08 06:38:51 +00:00
tutorial_activate ( app ) ;
2019-11-09 19:55:05 +00:00
local_persist Tutorial_Slide_Function * slides [ ] = {
default_4coder_tutorial_slide_1 ,
default_4coder_tutorial_slide_2 ,
} ;
tutorial . slide_index = 0 ;
tutorial . slide_func_ptrs = slides ;
tutorial . slide_count = ArrayCount ( slides ) ;
view_enqueue_command_function ( app , tutorial . view , default_4coder_tutorial_run ) ;
2019-11-08 04:58:13 +00:00
}
}
// BOTTOM