mirror of
				https://github.com/nicbarker/clay.git
				synced 2025-10-31 23:06:18 +00:00 
			
		
		
		
	[Renderers/Terminal] Add initial implementation of terminal renderer (#91)
This commit is contained in:
		
							parent
							
								
									7af50d0f48
								
							
						
					
					
						commit
						65e813d4df
					
				|  | @ -25,6 +25,7 @@ endif() | |||
| if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_DEMOS) | ||||
|   if(NOT MSVC) | ||||
|     add_subdirectory("examples/clay-official-website") | ||||
|     add_subdirectory("examples/terminal-example") | ||||
|   endif() | ||||
|   add_subdirectory("examples/introducing-clay-video-demo") | ||||
| endif () | ||||
|  |  | |||
							
								
								
									
										15
									
								
								examples/terminal-example/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								examples/terminal-example/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | |||
| cmake_minimum_required(VERSION 3.27) | ||||
| project(clay_examples_terminal C) | ||||
| set(CMAKE_C_STANDARD 99) | ||||
| 
 | ||||
| add_executable(clay_examples_terminal main.c) | ||||
| 
 | ||||
| target_compile_options(clay_examples_terminal PUBLIC) | ||||
| target_include_directories(clay_examples_terminal PUBLIC .) | ||||
| if (CMAKE_SYSTEM_NAME STREQUAL Linux) | ||||
|     target_link_libraries(clay_examples_terminal INTERFACE m) | ||||
| endif() | ||||
| 
 | ||||
| target_link_libraries(clay_examples_terminal PUBLIC ${CURSES_LIBRARY}) | ||||
| set(CMAKE_CXX_FLAGS_DEBUG "-Wall -Werror -DCLAY_DEBUG") | ||||
| set(CMAKE_CXX_FLAGS_RELEASE "-O3") | ||||
							
								
								
									
										39
									
								
								examples/terminal-example/main.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								examples/terminal-example/main.c
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,39 @@ | |||
| // Must be defined in one file, _before_ #include "clay.h"
 | ||||
| #define CLAY_IMPLEMENTATION | ||||
| 
 | ||||
| #include <unistd.h> | ||||
| #include "../../clay.h" | ||||
| #include "../../renderers/terminal/clay_renderer_terminal_ansi.c" | ||||
| #include "../shared-layouts/clay-video-demo.c" | ||||
| 
 | ||||
| const Clay_Color COLOR_LIGHT = (Clay_Color) {224, 215, 210, 255}; | ||||
| const Clay_Color COLOR_RED = (Clay_Color) {168, 66, 28, 255}; | ||||
| const Clay_Color COLOR_ORANGE = (Clay_Color) {225, 138, 50, 255}; | ||||
| 
 | ||||
| void HandleClayErrors(Clay_ErrorData errorData) { | ||||
|     printf("%s", errorData.errorText.chars); | ||||
| } | ||||
| 
 | ||||
| int main() { | ||||
|     const int width = 145; | ||||
|     const int height = 41; | ||||
|     int columnWidth = 16; | ||||
| 
 | ||||
|     uint64_t totalMemorySize = Clay_MinMemorySize(); | ||||
|     Clay_Arena arena = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize)); | ||||
|     Clay_Initialize(arena, | ||||
|                     (Clay_Dimensions) {.width = (float) width * columnWidth, .height = (float) height * columnWidth}, | ||||
|                     (Clay_ErrorHandler) {HandleClayErrors}); | ||||
|     // Tell clay how to measure text
 | ||||
|     Clay_SetMeasureTextFunction(Console_MeasureText, &columnWidth); | ||||
|     ClayVideoDemo_Data demoData = ClayVideoDemo_Initialize(); | ||||
| 
 | ||||
|     while (true) { | ||||
|         Clay_RenderCommandArray renderCommands = ClayVideoDemo_CreateLayout(&demoData); | ||||
| 
 | ||||
|         Clay_Terminal_Render(renderCommands, width, height, columnWidth); | ||||
| 
 | ||||
|         fflush(stdout); | ||||
|         sleep(1); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										194
									
								
								renderers/terminal/clay_renderer_terminal_ansi.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										194
									
								
								renderers/terminal/clay_renderer_terminal_ansi.c
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,194 @@ | |||
| #include "stdint.h" | ||||
| #include "string.h" | ||||
| #include "stdio.h" | ||||
| #include "stdlib.h" | ||||
| 
 | ||||
| #ifdef CLAY_OVERFLOW_TRAP | ||||
| #include "signal.h" | ||||
| #endif | ||||
| 
 | ||||
| static inline void Console_MoveCursor(int x, int y) { | ||||
|     printf("\033[%d;%dH", y + 1, x + 1); | ||||
| } | ||||
| 
 | ||||
| bool Clay_PointIsInsideRect(Clay_Vector2 point, Clay_BoundingBox rect) { | ||||
|     // TODO this function is a copy of Clay__PointIsInsideRect but that one is internal, I don't know if we want
 | ||||
|     // TODO to expose Clay__PointIsInsideRect
 | ||||
|     return point.x >= rect.x && point.x < rect.x + rect.width && point.y >= rect.y && point.y < rect.y + rect.height; | ||||
| } | ||||
| 
 | ||||
| static inline void Console_DrawRectangle(int x0, int y0, int width, int height, Clay_Color color, | ||||
|                                          Clay_BoundingBox scissorBox) { | ||||
|     float average = (color.r + color.g + color.b + color.a) / 4 / 255; | ||||
| 
 | ||||
|     for (int y = y0; y < height + y0; y++) { | ||||
|         for (int x = x0; x < width + x0; x++) { | ||||
|             if (!Clay_PointIsInsideRect((Clay_Vector2) {.x = x, .y = y}, scissorBox)) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             Console_MoveCursor(x, y); | ||||
|             // TODO this should be replaced by a better logarithmic scale if we're doing black and white
 | ||||
|             if (average > 0.75) { | ||||
|                 printf("█"); | ||||
|             } else if (average > 0.5) { | ||||
|                 printf("▓"); | ||||
|             } else if (average > 0.25) { | ||||
|                 printf("▒"); | ||||
|             } else { | ||||
|                 printf("░"); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static inline Clay_Dimensions | ||||
| Console_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData) { | ||||
|     Clay_Dimensions textSize = {0}; | ||||
|     int columnWidth = *(int *) userData; | ||||
| 
 | ||||
|     // TODO this function is very wrong, it measures in characters, I have no idea what is the size in pixels
 | ||||
| 
 | ||||
|     float maxTextWidth = 0.0f; | ||||
|     float lineTextWidth = 0; | ||||
| 
 | ||||
|     float textHeight = 1; | ||||
| 
 | ||||
|     for (int i = 0; i < text.length; ++i) { | ||||
|         if (text.chars[i] == '\n') { | ||||
|             maxTextWidth = maxTextWidth > lineTextWidth ? maxTextWidth : lineTextWidth; | ||||
|             lineTextWidth = 0; | ||||
|             textHeight++; | ||||
|             continue; | ||||
|         } | ||||
|         lineTextWidth++; | ||||
|     } | ||||
| 
 | ||||
|     maxTextWidth = maxTextWidth > lineTextWidth ? maxTextWidth : lineTextWidth; | ||||
| 
 | ||||
|     textSize.width = maxTextWidth * columnWidth; | ||||
|     textSize.height = textHeight * columnWidth; | ||||
| 
 | ||||
|     return textSize; | ||||
| } | ||||
| 
 | ||||
| void Clay_Terminal_Render(Clay_RenderCommandArray renderCommands, int width, int height, int columnWidth) { | ||||
|     printf("\033[H\033[J"); // Clear
 | ||||
| 
 | ||||
|     const Clay_BoundingBox fullWindow = { | ||||
|             .x = 0, | ||||
|             .y = 0, | ||||
|             .width = (float) width, | ||||
|             .height = (float) height, | ||||
|     }; | ||||
| 
 | ||||
|     Clay_BoundingBox scissorBox = fullWindow; | ||||
| 
 | ||||
|     for (int j = 0; j < renderCommands.length; j++) { | ||||
|         Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, j); | ||||
|         Clay_BoundingBox boundingBox = (Clay_BoundingBox) { | ||||
|                 .x = (int)((renderCommand->boundingBox.x / columnWidth) + 0.5), | ||||
|                 .y = (int)((renderCommand->boundingBox.y / columnWidth) + 0.5), | ||||
|                 .width = (int)((renderCommand->boundingBox.width / columnWidth) + 0.5), | ||||
|                 .height = (int)((renderCommand->boundingBox.height / columnWidth) + 0.5), | ||||
|         }; | ||||
|         switch (renderCommand->commandType) { | ||||
|             case CLAY_RENDER_COMMAND_TYPE_TEXT: { | ||||
|                 Clay_TextRenderData data = renderCommand->renderData.text; | ||||
|                 Clay_StringSlice text = data.stringContents; | ||||
|                 int y = 0; | ||||
|                 for (int x = 0; x < text.length; x++) { | ||||
|                     if (text.chars[x] == '\n') { | ||||
|                         y++; | ||||
|                         continue; | ||||
|                     } | ||||
| 
 | ||||
|                     int cursorX = (int) boundingBox.x + x; | ||||
|                     int cursorY = (int) boundingBox.y + y; | ||||
|                     if (cursorY > scissorBox.y + scissorBox.height) { | ||||
|                         break; | ||||
|                     } | ||||
|                     if (!Clay_PointIsInsideRect((Clay_Vector2) {.x = cursorX, .y = cursorY}, scissorBox)) { | ||||
|                         continue; | ||||
|                     } | ||||
| 
 | ||||
|                     Console_MoveCursor(cursorX, cursorY); | ||||
|                     printf("%c", text.chars[x]); | ||||
|                 } | ||||
|                 break; | ||||
|             } | ||||
|             case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { | ||||
|                 scissorBox = boundingBox; | ||||
|                 break; | ||||
|             } | ||||
|             case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: { | ||||
|                 scissorBox = fullWindow; | ||||
|                 break; | ||||
|             } | ||||
|             case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { | ||||
|                 Clay_RectangleRenderData data = renderCommand->renderData.rectangle; | ||||
|                 Console_DrawRectangle( | ||||
|                         (int) boundingBox.x, | ||||
|                         (int) boundingBox.y, | ||||
|                         (int) boundingBox.width, | ||||
|                         (int) boundingBox.height, | ||||
|                         data.backgroundColor, | ||||
|                         scissorBox); | ||||
|                 break; | ||||
|             } | ||||
|             case CLAY_RENDER_COMMAND_TYPE_BORDER: { | ||||
|                 Clay_BorderRenderData data = renderCommand->renderData.border; | ||||
|                 // Left border
 | ||||
|                 if (data.width.left > 0) { | ||||
|                     Console_DrawRectangle( | ||||
|                             (int) (boundingBox.x), | ||||
|                             (int) (boundingBox.y + data.cornerRadius.topLeft), | ||||
|                             (int) data.width.left, | ||||
|                             (int) (boundingBox.height - data.cornerRadius.topLeft - data.cornerRadius.bottomLeft), | ||||
|                             data.color, | ||||
|                             scissorBox); | ||||
|                 } | ||||
|                 // Right border
 | ||||
|                 if (data.width.right > 0) { | ||||
|                     Console_DrawRectangle( | ||||
|                             (int) (boundingBox.x + boundingBox.width - data.width.right), | ||||
|                             (int) (boundingBox.y + data.cornerRadius.topRight), | ||||
|                             (int) data.width.right, | ||||
|                             (int) (boundingBox.height - data.cornerRadius.topRight - data.cornerRadius.bottomRight), | ||||
|                             data.color, | ||||
|                             scissorBox); | ||||
|                 } | ||||
|                 // Top border
 | ||||
|                 if (data.width.top > 0) { | ||||
|                     Console_DrawRectangle( | ||||
|                             (int) (boundingBox.x + data.cornerRadius.topLeft), | ||||
|                             (int) (boundingBox.y), | ||||
|                             (int) (boundingBox.width - data.cornerRadius.topLeft - data.cornerRadius.topRight), | ||||
|                             (int) data.width.top, | ||||
|                             data.color, | ||||
|                             scissorBox); | ||||
|                 } | ||||
|                 // Bottom border
 | ||||
|                 if (data.width.bottom > 0) { | ||||
|                     Console_DrawRectangle( | ||||
|                             (int) (boundingBox.x + data.cornerRadius.bottomLeft), | ||||
|                             (int) (boundingBox.y + boundingBox.height - data.width.bottom), | ||||
|                             (int) (boundingBox.width - data.cornerRadius.bottomLeft - data.cornerRadius.bottomRight), | ||||
|                             (int) data.width.bottom, | ||||
|                             data.color, | ||||
|                             scissorBox); | ||||
|                 } | ||||
|                 break; | ||||
|             } | ||||
|             default: { | ||||
|                 printf("Error: unhandled render command."); | ||||
| #ifdef CLAY_OVERFLOW_TRAP | ||||
|                 raise(SIGTRAP); | ||||
| #endif | ||||
|                 exit(1); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     Console_MoveCursor(-1, -1);  // TODO make the user not be able to write
 | ||||
| } | ||||
		Loading…
	
		Reference in a new issue
	
	 Emmanuel
						Emmanuel