/* 4coder_examples.cpp - Commands that are included mainly to serve as example code for customization writers. */ // TOP // tags: history; group // example-of: History_Group; history_group_begin; history_group_end CUSTOM_COMMAND_SIG(double_backspace) CUSTOM_DOC("Example of history group helpers") { /* History_Group is a wrapper around the history API that makes it easy to group any series of edits into a single undo/redo record in the buffer's history. Before any edits call history_group_begin and afterwards call history_group_end. After history_group_end all of the edits to the buffer supplied in history_group_begin will be merged, including all edits from function and command calls. */ View_ID view = get_active_view(app, Access_ReadWriteVisible); Buffer_ID buffer = view_get_buffer(app, view, Access_ReadWriteVisible); History_Group group = history_group_begin(app, buffer); backspace_char(app); backspace_char(app); history_group_end(group); } // tags: query; bar // example-of: Query_Bar CUSTOM_COMMAND_SIG(play_with_a_counter) CUSTOM_DOC("Example of query bar") { /* Query bars make a quick lightweight display of a single line of text for interactive commands, while still showing the buffer. Query bars are convenient because they don't require any complex UI setup, or extra rendering work inside your command. First, constructing a Query_Bar_Group is a convenient way to make sure the query bar states are fully cleaned up when the command ends. Second, we make our query bar and start showing it with start_query_bar. Until we call end_query_bar on the same bar, or the group's destructor runs, the bar struct needs to remain in memory. The easy way to accomplish this is to just let the bar be on the commands stack frame. */ i32 counter = 0; Query_Bar_Group group(app); Query_Bar dumb_bar = {}; dumb_bar.prompt = SCu8("Goes away at >= 10"); if (!start_query_bar(app, &dumb_bar, 0)){ return; } Query_Bar bar = {}; bar.prompt = SCu8("Counter = "); bar.string = SCu8(""); if (!start_query_bar(app, &bar, 0)){ return; } for (;;){ /* Notice here, we set the string of the query bar BEFORE we call get_next_input. get_next_input blocks this command until the next input is sent from the core. Whatever string we put in the bar now will be shown and remain in the bar until an event wakes up this command and we get a chance to modify the bar again. */ Scratch_Block scratch(app); bar.string = push_stringf(scratch, "%d", counter); if (counter >= 10){ end_query_bar(app, &dumb_bar, 0); } User_Input in = get_next_input(app, EventPropertyGroup_Any, EventProperty_Escape); if (in.abort){ break; } if (match_key_code(&in.event, KeyCode_Up)){ counter += 1; } else if (match_key_code(&in.event, KeyCode_Down)){ counter -= 1; } else{ leave_current_input_unhandled(app); } } } // tags: input; loop // example-of: get_next_input; leave_current_input_unhandled CUSTOM_COMMAND_SIG(display_key_codes) CUSTOM_DOC("Example of input handling loop") { /* In the 4coder custom layer, inputs are handled by a view context. A view context is a thread that hands off control with the main thread of the 4coder core. When a command is running in a view context thread, it can wait for inputs from the core by calling get_next_input. If your command gets inputs from the core, then default input handling isn't happening, so command bindings don't trigger unless you trigger them yourself. */ Query_Bar_Group group(app); Query_Bar bar = {}; bar.prompt = SCu8("KeyCode = "); if (!start_query_bar(app, &bar, 0)){ return; } Key_Code code = 0; b32 is_dead_key = false; for (;;){ Scratch_Block scratch(app); if (code == 0){ bar.string = SCu8("..."); } else{ bar.string = push_stringf(scratch, "KeyCode_%s (%d)%s", key_code_name[code], code, is_dead_key?" dead-key":""); } User_Input in = get_next_input(app, EventPropertyGroup_Any, EventProperty_Escape); if (in.abort){ break; } if (in.event.kind == InputEventKind_KeyStroke){ code = in.event.key.code; is_dead_key = event_is_dead_key(&in.event); } else{ /* Marking inputs as handled lets the core determine if certain inputs should be passed to additional handlers. This is especially important for text input, which is explained more in the example display_text_input. */ leave_current_input_unhandled(app); } } } // tags: text; input // example-of: get_next_input; leave_current_input_unhandled; to_writable CUSTOM_COMMAND_SIG(display_text_input) CUSTOM_DOC("Example of to_writable and leave_current_input_unhandled") { /* In the 4coder custom layer, inputs are handled by a view context. A view context is a thread that hands off control with the main thread of the 4coder core. When a command is running in a view context thread, it can wait for inputs from the core by calling get_next_input. If your command gets inputs from the core, then default input handling isn't happening, so command bindings don't trigger unless you trigger them yourself. */ Query_Bar_Group group(app); Query_Bar bar = {}; bar.prompt = SCu8("Weird String: "); if (!start_query_bar(app, &bar, 0)){ return; } u8 buffer[256]; u64 size = 0; for (;;){ User_Input in = get_next_input(app, EventPropertyGroup_Any, EventProperty_Escape); if (in.abort){ break; } String_Const_u8 in_string = to_writable(&in); if (in_string.size > 0){ size = clamp_top(in_string.size, sizeof(buffer)); block_copy(buffer, in_string.str, size); bar.string = SCu8(buffer, size); } else if (in.event.kind == InputEventKind_KeyStroke){ /* If we handle a key stroke then the core marks any text input generated from that key stroke as handled too, and the text input is never passed. By marking key strokes as unhandled, we ensure we get text input events. */ leave_current_input_unhandled(app); } } } // tags: string; number; query; user // example-of: query_user_string; query_user_number CUSTOM_COMMAND_SIG(string_repeat) CUSTOM_DOC("Example of query_user_string and query_user_number") { Query_Bar_Group group(app); Query_Bar string_bar = {}; string_bar.prompt = SCu8("String: "); u8 string_buffer[KB(1)]; string_bar.string.str = string_buffer; string_bar.string_capacity = sizeof(string_buffer); Query_Bar number_bar = {}; number_bar.prompt = SCu8("Repeat Count: "); u8 number_buffer[KB(1)]; number_bar.string.str = number_buffer; number_bar.string_capacity = sizeof(number_buffer); if (query_user_string(app, &string_bar)){ if (string_bar.string.size > 0){ if (query_user_number(app, &number_bar)){ if (number_bar.string.size > 0){ i32 repeats = (i32)string_to_integer(number_bar.string, 10); repeats = clamp_top(repeats, 1000); Scratch_Block scratch(app); String_Const_u8 msg = push_stringf(scratch, "%.*s\n", string_expand(string_bar.string)); for (i32 i = 0; i < repeats; i += 1){ print_message(app, msg); } } } } } } global Audio_Control the_music_control = {}; CUSTOM_COMMAND_SIG(music_start) CUSTOM_DOC("Starts the music.") { local_persist Audio_Clip the_music_clip = {}; if (the_music_clip.sample_count == 0){ Scratch_Block scratch(app); FILE *file = def_search_normal_fopen(scratch, "audio_test/chtulthu.wav", "rb"); the_music_clip = audio_clip_from_wav_FILE(&global_permanent_arena, file); fclose(file); } if (!def_audio_is_playing(&the_music_control)){ the_music_control.loop = true; the_music_control.channel_volume[0] = 1.f; the_music_control.channel_volume[1] = 1.f; def_audio_play_clip(the_music_clip, &the_music_control); } } CUSTOM_COMMAND_SIG(music_stop) CUSTOM_DOC("Stops the music.") { def_audio_stop(&the_music_control); } CUSTOM_COMMAND_SIG(hit_sfx) CUSTOM_DOC("Play the hit sound effect") { local_persist Audio_Clip the_hit_clip = {}; if (the_hit_clip.sample_count == 0){ Scratch_Block scratch(app); FILE *file = def_search_normal_fopen(scratch, "audio_test/hit.wav", "rb"); the_hit_clip = audio_clip_from_wav_FILE(&global_permanent_arena, file); fclose(file); } local_persist u32 index = 0; local_persist Audio_Control controls[8] = {}; Audio_Control *control = &controls[index%8]; if (!def_audio_is_playing(control)){ control->loop = false; control->channel_volume[0] = 1.f; control->channel_volume[1] = 1.f; def_audio_play_clip(the_hit_clip, control); index += 1; } } // BOTTOM