diff --git a/Assets/stylesheet.css b/Assets/stylesheet.css index e7a2c21c..8621e6db 100644 --- a/Assets/stylesheet.css +++ b/Assets/stylesheet.css @@ -364,6 +364,10 @@ TabView { spacing: 4; } +.tabViewController { + autoresizing-mask: contain; +} + TabView > .tabPageView { border-color: silver; border-radius: 4; diff --git a/Assets/stylesheet.css.h b/Assets/stylesheet.css.h index b9de1c9f..3ab756c1 100644 --- a/Assets/stylesheet.css.h +++ b/Assets/stylesheet.css.h @@ -499,100 +499,105 @@ unsigned char stylesheet_css[] = { 0x69, 0x64, 0x74, 0x68, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x56, 0x69, 0x65, 0x77, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x34, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, - 0x54, 0x61, 0x62, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x2e, 0x74, - 0x61, 0x62, 0x50, 0x61, 0x67, 0x65, 0x56, 0x69, 0x65, 0x77, 0x20, 0x7b, - 0x0a, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x63, 0x6f, - 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x73, 0x69, 0x6c, 0x76, 0x65, 0x72, 0x3b, - 0x0a, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x72, 0x61, - 0x64, 0x69, 0x75, 0x73, 0x3a, 0x20, 0x34, 0x3b, 0x0a, 0x20, 0x20, 0x62, - 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, - 0x20, 0x31, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x56, 0x69, - 0x65, 0x77, 0x20, 0x3e, 0x20, 0x2e, 0x74, 0x61, 0x62, 0x53, 0x65, 0x6c, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x69, 0x65, 0x77, 0x20, 0x7b, - 0x0a, 0x20, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x69, 0x7a, - 0x69, 0x6e, 0x67, 0x2d, 0x6d, 0x61, 0x73, 0x6b, 0x3a, 0x20, 0x63, 0x6f, - 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x20, 0x7c, 0x20, 0x77, 0x69, 0x64, 0x74, - 0x68, 0x3b, 0x0a, 0x20, 0x20, 0x61, 0x78, 0x69, 0x73, 0x3a, 0x20, 0x68, - 0x6f, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x74, 0x61, 0x6c, 0x3b, 0x0a, 0x20, - 0x20, 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, - 0x6e, 0x3a, 0x20, 0x66, 0x69, 0x6c, 0x6c, 0x2d, 0x65, 0x71, 0x75, 0x61, - 0x6c, 0x6c, 0x79, 0x3b, 0x0a, 0x20, 0x20, 0x73, 0x70, 0x61, 0x63, 0x69, - 0x6e, 0x67, 0x3a, 0x20, 0x34, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, - 0x62, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x2e, 0x74, 0x61, 0x62, - 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x69, 0x65, - 0x77, 0x20, 0x3e, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x20, 0x7b, 0x0a, - 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x32, - 0x20, 0x32, 0x20, 0x32, 0x20, 0x32, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, - 0x61, 0x62, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x2e, 0x74, 0x61, - 0x62, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x69, - 0x65, 0x77, 0x20, 0x3e, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x2e, 0x73, - 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x20, 0x7b, 0x0a, 0x20, 0x20, - 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, - 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x64, 0x61, 0x72, 0x6b, 0x67, 0x72, - 0x61, 0x79, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x56, 0x69, - 0x65, 0x77, 0x20, 0x3e, 0x20, 0x2e, 0x74, 0x61, 0x62, 0x53, 0x65, 0x6c, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, - 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x2e, 0x64, 0x69, 0x73, 0x61, 0x62, - 0x6c, 0x65, 0x64, 0x20, 0x3e, 0x20, 0x54, 0x65, 0x78, 0x74, 0x20, 0x7b, - 0x0a, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x67, 0x72, - 0x65, 0x79, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x56, 0x69, - 0x65, 0x77, 0x20, 0x3e, 0x20, 0x2e, 0x74, 0x61, 0x62, 0x53, 0x65, 0x6c, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, - 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x20, 0x3e, 0x20, 0x54, 0x65, 0x78, - 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x6d, - 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x2d, - 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, - 0x65, 0x78, 0x74, 0x56, 0x69, 0x65, 0x77, 0x20, 0x7b, 0x0a, 0x20, 0x20, - 0x61, 0x75, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x69, 0x7a, 0x69, 0x6e, 0x67, - 0x2d, 0x6d, 0x61, 0x73, 0x6b, 0x3a, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, - 0x69, 0x6e, 0x3b, 0x0a, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, - 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, - 0x23, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x36, 0x36, 0x3b, 0x0a, 0x20, - 0x20, 0x62, 0x65, 0x76, 0x65, 0x6c, 0x3a, 0x20, 0x69, 0x6e, 0x73, 0x65, - 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x63, 0x6c, 0x69, 0x70, 0x73, 0x2d, 0x73, - 0x75, 0x62, 0x76, 0x69, 0x65, 0x77, 0x73, 0x3a, 0x20, 0x74, 0x72, 0x75, - 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x65, 0x64, 0x69, 0x74, 0x61, 0x62, 0x6c, - 0x65, 0x3a, 0x20, 0x74, 0x72, 0x75, 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x6d, - 0x69, 0x6e, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x36, - 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, - 0x3a, 0x20, 0x34, 0x20, 0x34, 0x20, 0x34, 0x20, 0x34, 0x3b, 0x0a, 0x7d, - 0x0a, 0x0a, 0x54, 0x65, 0x78, 0x74, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, - 0x20, 0x54, 0x65, 0x78, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x6c, - 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x6d, 0x69, 0x64, - 0x64, 0x6c, 0x65, 0x2d, 0x6c, 0x65, 0x66, 0x74, 0x3b, 0x0a, 0x7d, 0x0a, - 0x0a, 0x54, 0x65, 0x78, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x63, 0x6f, - 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x67, 0x68, 0x6f, 0x73, 0x74, 0x77, 0x68, - 0x69, 0x74, 0x65, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x2e, 0x63, 0x6f, 0x6e, - 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, + 0x2e, 0x74, 0x61, 0x62, 0x56, 0x69, 0x65, 0x77, 0x43, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x2d, 0x6d, 0x61, 0x73, 0x6b, 0x3a, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, - 0x6e, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x6c, 0x69, 0x67, 0x6e, - 0x6d, 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, - 0x61, 0x6c, 0x3b, 0x0a, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, - 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, - 0x63, 0x68, 0x61, 0x72, 0x63, 0x6f, 0x61, 0x6c, 0x3b, 0x0a, 0x20, 0x20, - 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, - 0x3a, 0x20, 0x23, 0x64, 0x65, 0x64, 0x65, 0x64, 0x65, 0x61, 0x61, 0x3b, - 0x0a, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x72, 0x61, - 0x64, 0x69, 0x75, 0x73, 0x3a, 0x20, 0x34, 0x3b, 0x0a, 0x20, 0x20, 0x62, - 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, - 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, - 0x67, 0x3a, 0x20, 0x32, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x2e, 0x6f, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x3e, 0x20, 0x4f, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, - 0x72, 0x2d, 0x72, 0x61, 0x64, 0x69, 0x75, 0x73, 0x3a, 0x20, 0x32, 0x3b, - 0x0a, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, - 0x32, 0x20, 0x34, 0x20, 0x32, 0x20, 0x34, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, - 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x3e, 0x20, 0x4f, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x20, - 0x7b, 0x0a, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, - 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x64, 0x61, - 0x72, 0x6b, 0x67, 0x72, 0x61, 0x79, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x2e, - 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x3e, 0x20, 0x4f, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, + 0x6e, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x56, 0x69, 0x65, + 0x77, 0x20, 0x3e, 0x20, 0x2e, 0x74, 0x61, 0x62, 0x50, 0x61, 0x67, 0x65, + 0x56, 0x69, 0x65, 0x77, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x62, 0x6f, 0x72, + 0x64, 0x65, 0x72, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x73, + 0x69, 0x6c, 0x76, 0x65, 0x72, 0x3b, 0x0a, 0x20, 0x20, 0x62, 0x6f, 0x72, + 0x64, 0x65, 0x72, 0x2d, 0x72, 0x61, 0x64, 0x69, 0x75, 0x73, 0x3a, 0x20, + 0x34, 0x3b, 0x0a, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, + 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x3b, 0x0a, 0x7d, 0x0a, + 0x0a, 0x54, 0x61, 0x62, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x2e, + 0x74, 0x61, 0x62, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x56, 0x69, 0x65, 0x77, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x75, 0x74, + 0x6f, 0x72, 0x65, 0x73, 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x2d, 0x6d, 0x61, + 0x73, 0x6b, 0x3a, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x20, + 0x7c, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3b, 0x0a, 0x20, 0x20, 0x61, + 0x78, 0x69, 0x73, 0x3a, 0x20, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x6f, 0x6e, + 0x74, 0x61, 0x6c, 0x3b, 0x0a, 0x20, 0x20, 0x64, 0x69, 0x73, 0x74, 0x72, + 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x66, 0x69, 0x6c, + 0x6c, 0x2d, 0x65, 0x71, 0x75, 0x61, 0x6c, 0x6c, 0x79, 0x3b, 0x0a, 0x20, + 0x20, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x34, 0x3b, + 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x56, 0x69, 0x65, 0x77, 0x20, + 0x3e, 0x20, 0x2e, 0x74, 0x61, 0x62, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x4c, 0x61, + 0x62, 0x65, 0x6c, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, + 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x32, 0x20, 0x32, 0x20, 0x32, 0x20, 0x32, + 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x61, 0x62, 0x56, 0x69, 0x65, 0x77, + 0x20, 0x3e, 0x20, 0x2e, 0x74, 0x61, 0x62, 0x53, 0x65, 0x6c, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x4c, + 0x61, 0x62, 0x65, 0x6c, 0x2e, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, - 0x64, 0x69, 0x6d, 0x67, 0x72, 0x61, 0x79, 0x3b, 0x0a, 0x7d, 0x0a, 0x00 + 0x64, 0x61, 0x72, 0x6b, 0x67, 0x72, 0x61, 0x79, 0x3b, 0x0a, 0x7d, 0x0a, + 0x0a, 0x54, 0x61, 0x62, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x2e, + 0x74, 0x61, 0x62, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, + 0x2e, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x20, 0x3e, 0x20, + 0x54, 0x65, 0x78, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x63, 0x6f, 0x6c, + 0x6f, 0x72, 0x3a, 0x20, 0x67, 0x72, 0x65, 0x79, 0x3b, 0x0a, 0x7d, 0x0a, + 0x0a, 0x54, 0x61, 0x62, 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x2e, + 0x74, 0x61, 0x62, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x4c, 0x61, 0x62, 0x65, 0x6c, + 0x20, 0x3e, 0x20, 0x54, 0x65, 0x78, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, + 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x6d, + 0x69, 0x64, 0x64, 0x6c, 0x65, 0x2d, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, + 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x65, 0x78, 0x74, 0x56, 0x69, 0x65, + 0x77, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x72, 0x65, + 0x73, 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x2d, 0x6d, 0x61, 0x73, 0x6b, 0x3a, + 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x3b, 0x0a, 0x20, 0x20, + 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, + 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x36, 0x36, 0x3b, 0x0a, 0x20, 0x20, 0x62, 0x65, 0x76, 0x65, 0x6c, + 0x3a, 0x20, 0x69, 0x6e, 0x73, 0x65, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x63, + 0x6c, 0x69, 0x70, 0x73, 0x2d, 0x73, 0x75, 0x62, 0x76, 0x69, 0x65, 0x77, + 0x73, 0x3a, 0x20, 0x74, 0x72, 0x75, 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x65, + 0x64, 0x69, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x3a, 0x20, 0x74, 0x72, 0x75, + 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x2d, 0x77, 0x69, 0x64, + 0x74, 0x68, 0x3a, 0x20, 0x31, 0x36, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x70, + 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x34, 0x20, 0x34, 0x20, + 0x34, 0x20, 0x34, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x65, 0x78, 0x74, + 0x56, 0x69, 0x65, 0x77, 0x20, 0x3e, 0x20, 0x54, 0x65, 0x78, 0x74, 0x20, + 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, + 0x74, 0x3a, 0x20, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x2d, 0x6c, 0x65, + 0x66, 0x74, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x54, 0x65, 0x78, 0x74, 0x20, + 0x7b, 0x0a, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x67, + 0x68, 0x6f, 0x73, 0x74, 0x77, 0x68, 0x69, 0x74, 0x65, 0x3b, 0x0a, 0x7d, + 0x0a, 0x0a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, + 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x72, 0x65, 0x73, + 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x2d, 0x6d, 0x61, 0x73, 0x6b, 0x3a, 0x20, + 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, + 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x7b, 0x0a, 0x20, + 0x20, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x3a, 0x20, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x3b, 0x0a, 0x20, 0x20, + 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, + 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x63, 0x68, 0x61, 0x72, 0x63, 0x6f, + 0x61, 0x6c, 0x3b, 0x0a, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, + 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x64, 0x65, 0x64, + 0x65, 0x64, 0x65, 0x61, 0x61, 0x3b, 0x0a, 0x20, 0x20, 0x62, 0x6f, 0x72, + 0x64, 0x65, 0x72, 0x2d, 0x72, 0x61, 0x64, 0x69, 0x75, 0x73, 0x3a, 0x20, + 0x34, 0x3b, 0x0a, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, + 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, + 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x32, 0x3b, 0x0a, + 0x7d, 0x0a, 0x0a, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, + 0x3e, 0x20, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x7b, 0x0a, 0x20, + 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x72, 0x61, 0x64, 0x69, + 0x75, 0x73, 0x3a, 0x20, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x70, 0x61, 0x64, + 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x32, 0x20, 0x34, 0x20, 0x32, 0x20, + 0x34, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x20, 0x3e, 0x20, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3a, + 0x68, 0x6f, 0x76, 0x65, 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x62, 0x61, + 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, + 0x6f, 0x72, 0x3a, 0x20, 0x64, 0x61, 0x72, 0x6b, 0x67, 0x72, 0x61, 0x79, + 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x20, 0x3e, 0x20, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x73, + 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x20, 0x7b, 0x0a, 0x20, 0x20, + 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, + 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x64, 0x69, 0x6d, 0x67, 0x72, 0x61, + 0x79, 0x3b, 0x0a, 0x7d, 0x0a, 0x00 }; -unsigned int stylesheet_css_len = 7139; +unsigned int stylesheet_css_len = 7193; diff --git a/ObjectivelyMVC.xcodeproj/project.pbxproj b/ObjectivelyMVC.xcodeproj/project.pbxproj index 59932995..423b9753 100644 --- a/ObjectivelyMVC.xcodeproj/project.pbxproj +++ b/ObjectivelyMVC.xcodeproj/project.pbxproj @@ -28,6 +28,9 @@ dependencies = ( CE0C2218304CC1FD008A6A2A /* PBXTargetDependency */, CED40A98304B2DFA0090C57A /* PBXTargetDependency */, + B3DD5991CDBD4582092CC4F6 /* PBXTargetDependency */, + 00E123F744F252F431ACB5AB /* PBXTargetDependency */, + 0850FEAA7E79310A5E7C5ED5 /* PBXTargetDependency */, CED40A633046145B0090C57A /* PBXTargetDependency */, CE34C8A51FB2A0210025F231 /* PBXTargetDependency */, CE88199A1F91B0DA000D5AB7 /* PBXTargetDependency */, @@ -212,10 +215,25 @@ CEBA3F8C2FF3F25E00758CA6 /* Hello.vert.metal in CopyFiles */ = {isa = PBXBuildFile; fileRef = HS00000000000000000000A2 /* Hello.vert.metal */; }; CEBCB6D521664EB1009E6289 /* Types.h in Headers */ = {isa = PBXBuildFile; fileRef = CEF1D8BB1D4704950099A857 /* Types.h */; settings = {ATTRIBUTES = (Public, ); }; }; CED40A8D304B2D700090C57A /* SDL3.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = B1AA0001000000000000000A /* SDL3.xcframework */; }; + 27826E51CD9BF334A46F8B74 /* SDL3.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = B1AA0001000000000000000A /* SDL3.xcframework */; }; + BB87084D13A301A5A22CBCA2 /* SDL3.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = B1AA0001000000000000000A /* SDL3.xcframework */; }; + 98BFE75CAAA4EB3E01C703AE /* SDL3.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = B1AA0001000000000000000A /* SDL3.xcframework */; }; CED40A8E304B2D700090C57A /* Objectively.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE73E9FA2FEEA5F60048BC24 /* Objectively.framework */; }; + C98EF9B35812D5CEDCD4285E /* Objectively.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE73E9FA2FEEA5F60048BC24 /* Objectively.framework */; }; + 254D1795460F9E287313E070 /* Objectively.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE73E9FA2FEEA5F60048BC24 /* Objectively.framework */; }; + 9BDEC2F3260285F60247A6D7 /* Objectively.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE73E9FA2FEEA5F60048BC24 /* Objectively.framework */; }; CED40A8F304B2D700090C57A /* ObjectivelyGPU.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEEAAFF52FED7E6000FFEBE6 /* ObjectivelyGPU.framework */; }; + E85C0A9A11FE4DBF991CB55C /* ObjectivelyGPU.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEEAAFF52FED7E6000FFEBE6 /* ObjectivelyGPU.framework */; }; + A45306E1A2B9F91C43A3D035 /* ObjectivelyGPU.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEEAAFF52FED7E6000FFEBE6 /* ObjectivelyGPU.framework */; }; + 7C9B4270AF0FF5BBEBC68236 /* ObjectivelyGPU.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEEAAFF52FED7E6000FFEBE6 /* ObjectivelyGPU.framework */; }; CED40A90304B2D700090C57A /* ObjectivelyMVC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CED157CD1C4BF3AC00FBA2DE /* ObjectivelyMVC.framework */; }; + 53D16432B2CCCE4F3956685E /* ObjectivelyMVC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CED157CD1C4BF3AC00FBA2DE /* ObjectivelyMVC.framework */; }; + A95FB52C4A3FB476D42F4A5E /* ObjectivelyMVC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CED157CD1C4BF3AC00FBA2DE /* ObjectivelyMVC.framework */; }; + 66EA7319EF11A582FD1064E2 /* ObjectivelyMVC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CED157CD1C4BF3AC00FBA2DE /* ObjectivelyMVC.framework */; }; CED40A96304B2D7F0090C57A /* ImageAtlas.c in Sources */ = {isa = PBXBuildFile; fileRef = CED40A86304B2D550090C57A /* ImageAtlas.c */; }; + 5F8A115B0069B76D5DF21618 /* Image.c in Sources */ = {isa = PBXBuildFile; fileRef = FEDCD6FF3EFA7A4388486410 /* Image.c */; }; + B25F30D62819F943DCBC5293 /* Text.c in Sources */ = {isa = PBXBuildFile; fileRef = A2A76A66C35A8EA25E73AED3 /* Text.c */; }; + DCB5AE1270D2C8B8C4E44027 /* TextView.c in Sources */ = {isa = PBXBuildFile; fileRef = 02533D62677918BD9C9D8632 /* TextView.c */; }; CED56FF62FE3440000756996 /* SDL3.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = B1AA0001000000000000000A /* SDL3.xcframework */; }; CED56FF92FE3440000756996 /* SDL3.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = B1AA0001000000000000000A /* SDL3.xcframework */; }; CEEAAFF62FED7E6000FFEBE6 /* ObjectivelyGPU.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEEAAFF52FED7E6000FFEBE6 /* ObjectivelyGPU.framework */; }; @@ -345,6 +363,27 @@ remoteGlobalIDString = CED157CC1C4BF3AC00FBA2DE; remoteInfo = ObjectivelyMVC; }; + EDC8B69B4C254E0B20983FBD /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = CED157931C4BEA8900FBA2DE /* Project object */; + proxyType = 1; + remoteGlobalIDString = CED157CC1C4BF3AC00FBA2DE; + remoteInfo = ObjectivelyMVC; + }; + 6CE99E2EEC2CA9CACE23F199 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = CED157931C4BEA8900FBA2DE /* Project object */; + proxyType = 1; + remoteGlobalIDString = CED157CC1C4BF3AC00FBA2DE; + remoteInfo = ObjectivelyMVC; + }; + 7EE94EDE9901ED9472A2194D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = CED157931C4BEA8900FBA2DE /* Project object */; + proxyType = 1; + remoteGlobalIDString = CED157CC1C4BF3AC00FBA2DE; + remoteInfo = ObjectivelyMVC; + }; CED40A97304B2DFA0090C57A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = CED157931C4BEA8900FBA2DE /* Project object */; @@ -352,6 +391,27 @@ remoteGlobalIDString = CED40A87304B2D700090C57A; remoteInfo = "ObjectivelyMVC-ImageAtlas"; }; + 53F472CDBD720DEE99451939 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = CED157931C4BEA8900FBA2DE /* Project object */; + proxyType = 1; + remoteGlobalIDString = 49B779A4A2BCD3DCD3C3BD06; + remoteInfo = "ObjectivelyMVC-Image"; + }; + 288C55F591D0A32135B0B1D8 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = CED157931C4BEA8900FBA2DE /* Project object */; + proxyType = 1; + remoteGlobalIDString = 563130823413BCCA09134760; + remoteInfo = "ObjectivelyMVC-Text"; + }; + 86612F2306FFCE232CE1BB04 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = CED157931C4BEA8900FBA2DE /* Project object */; + proxyType = 1; + remoteGlobalIDString = A73BC0FDBE1118F914A440F1; + remoteInfo = "ObjectivelyMVC-TextView"; + }; HUDA00000000000000000010 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = CED157931C4BEA8900FBA2DE /* Project object */; @@ -441,6 +501,33 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 9F0912651CABAEDD083DCF56 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 12; + dstPath = ""; + dstSubfolderSpec = 7; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F7CF640A30C7C28D712A607A /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 12; + dstPath = ""; + dstSubfolderSpec = 7; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 3B8576F5164522B222E465B0 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 12; + dstPath = ""; + dstSubfolderSpec = 7; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ @@ -635,7 +722,13 @@ CED157B51C4BF32A00FBA2DE /* Makefile.am */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Makefile.am; sourceTree = ""; }; CED157CD1C4BF3AC00FBA2DE /* ObjectivelyMVC.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ObjectivelyMVC.framework; sourceTree = BUILT_PRODUCTS_DIR; }; CED40A86304B2D550090C57A /* ImageAtlas.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ImageAtlas.c; sourceTree = ""; }; + FEDCD6FF3EFA7A4388486410 /* Image.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = Image.c; sourceTree = ""; }; + A2A76A66C35A8EA25E73AED3 /* Text.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = Text.c; sourceTree = ""; }; + 02533D62677918BD9C9D8632 /* TextView.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = TextView.c; sourceTree = ""; }; CED40A95304B2D700090C57A /* ObjectivelyMVC-ImageAtlas */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "ObjectivelyMVC-ImageAtlas"; sourceTree = BUILT_PRODUCTS_DIR; }; + 4A4E9B8FCA4EC2C5AF4D4C30 /* ObjectivelyMVC-Image */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "ObjectivelyMVC-Image"; sourceTree = BUILT_PRODUCTS_DIR; }; + C3EB55F6FC6630DE5644B5A7 /* ObjectivelyMVC-Text */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "ObjectivelyMVC-Text"; sourceTree = BUILT_PRODUCTS_DIR; }; + 26EA4C5E6BBC55A50927A783 /* ObjectivelyMVC-TextView */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "ObjectivelyMVC-TextView"; sourceTree = BUILT_PRODUCTS_DIR; }; CEEAAFF52FED7E6000FFEBE6 /* ObjectivelyGPU.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ObjectivelyGPU.framework; sourceTree = BUILT_PRODUCTS_DIR; }; CEF1D88B1D4265A70099A857 /* Panel.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = Panel.c; sourceTree = ""; }; CEF1D88C1D4265A70099A857 /* Panel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = Panel.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; @@ -790,6 +883,45 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 86088652F18876526D6A2EB3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + CE0C221C304CC285008A6A2A /* SDL3_image.xcframework in Frameworks */, + CE0C221D304CC285008A6A2A /* SDL3_ttf.xcframework in Frameworks */, + 27826E51CD9BF334A46F8B74 /* SDL3.xcframework in Frameworks */, + C98EF9B35812D5CEDCD4285E /* Objectively.framework in Frameworks */, + E85C0A9A11FE4DBF991CB55C /* ObjectivelyGPU.framework in Frameworks */, + 53D16432B2CCCE4F3956685E /* ObjectivelyMVC.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DB3CFD7C69B22D18B0E48944 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + CE0C221C304CC285008A6A2A /* SDL3_image.xcframework in Frameworks */, + CE0C221D304CC285008A6A2A /* SDL3_ttf.xcframework in Frameworks */, + BB87084D13A301A5A22CBCA2 /* SDL3.xcframework in Frameworks */, + 254D1795460F9E287313E070 /* Objectively.framework in Frameworks */, + A45306E1A2B9F91C43A3D035 /* ObjectivelyGPU.framework in Frameworks */, + A95FB52C4A3FB476D42F4A5E /* ObjectivelyMVC.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E42EC99E530339388B01BE5D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + CE0C221C304CC285008A6A2A /* SDL3_image.xcframework in Frameworks */, + CE0C221D304CC285008A6A2A /* SDL3_ttf.xcframework in Frameworks */, + 98BFE75CAAA4EB3E01C703AE /* SDL3.xcframework in Frameworks */, + 9BDEC2F3260285F60247A6D7 /* Objectively.framework in Frameworks */, + 7C9B4270AF0FF5BBEBC68236 /* ObjectivelyGPU.framework in Frameworks */, + 66EA7319EF11A582FD1064E2 /* ObjectivelyMVC.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; HUDA00000000000000000009 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -939,10 +1071,13 @@ isa = PBXGroup; children = ( CE0C2206304CBC73008A6A2A /* Font+Bitmap.c */, + FEDCD6FF3EFA7A4388486410 /* Image.c */, CED40A86304B2D550090C57A /* ImageAtlas.c */, CE55C1BB1F944C1B00D5A326 /* Selector.c */, CE8819891F91B094000D5AB7 /* Style.c */, CE34C8941FB29F3A0025F231 /* Stylesheet.c */, + A2A76A66C35A8EA25E73AED3 /* Text.c */, + 02533D62677918BD9C9D8632 /* TextView.c */, 58441034DC162E9BE53173F4 /* View.c */, CE423A2C1F534127002767E7 /* Makefile.am */, ); @@ -1092,6 +1227,9 @@ B1AA000100000000000000ED /* ObjectivelyMVC-Hello-iOS.app */, 84BBDF9467CD6F80ACF8AADF /* ObjectivelyMVC-View */, CED40A95304B2D700090C57A /* ObjectivelyMVC-ImageAtlas */, + 4A4E9B8FCA4EC2C5AF4D4C30 /* ObjectivelyMVC-Image */, + C3EB55F6FC6630DE5644B5A7 /* ObjectivelyMVC-Text */, + 26EA4C5E6BBC55A50927A783 /* ObjectivelyMVC-TextView */, CE0C2215304CBC82008A6A2A /* ObjectivelyMVC-Font+Bitmap */, ); name = Products; @@ -1334,6 +1472,60 @@ productReference = CED40A95304B2D700090C57A /* ObjectivelyMVC-ImageAtlas */; productType = "com.apple.product-type.tool"; }; + 49B779A4A2BCD3DCD3C3BD06 /* ObjectivelyMVC-Image */ = { + isa = PBXNativeTarget; + buildConfigurationList = D44656D0AEE1A511C175339A /* Build configuration list for PBXNativeTarget "ObjectivelyMVC-Image" */; + buildPhases = ( + 9B8AE887D35458AE7C0EE060 /* Sources */, + 86088652F18876526D6A2EB3 /* Frameworks */, + 9F0912651CABAEDD083DCF56 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + C2DC6F82A5128ED1D64A0D28 /* PBXTargetDependency */, + ); + name = "ObjectivelyMVC-Image"; + productName = "ObjectivelyMVC-Image"; + productReference = 4A4E9B8FCA4EC2C5AF4D4C30 /* ObjectivelyMVC-Image */; + productType = "com.apple.product-type.tool"; + }; + 563130823413BCCA09134760 /* ObjectivelyMVC-Text */ = { + isa = PBXNativeTarget; + buildConfigurationList = 42DA443CE43697A0CE2778F7 /* Build configuration list for PBXNativeTarget "ObjectivelyMVC-Text" */; + buildPhases = ( + 243313ABE31DC93EA936F339 /* Sources */, + DB3CFD7C69B22D18B0E48944 /* Frameworks */, + F7CF640A30C7C28D712A607A /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + BA04F7324C595E256D51814C /* PBXTargetDependency */, + ); + name = "ObjectivelyMVC-Text"; + productName = "ObjectivelyMVC-Text"; + productReference = C3EB55F6FC6630DE5644B5A7 /* ObjectivelyMVC-Text */; + productType = "com.apple.product-type.tool"; + }; + A73BC0FDBE1118F914A440F1 /* ObjectivelyMVC-TextView */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7935DBBB394E57382160DE99 /* Build configuration list for PBXNativeTarget "ObjectivelyMVC-TextView" */; + buildPhases = ( + FE5F77E35CE051DA2EE56CCD /* Sources */, + E42EC99E530339388B01BE5D /* Frameworks */, + 3B8576F5164522B222E465B0 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + 10B40536A4C6ED7844DECE20 /* PBXTargetDependency */, + ); + name = "ObjectivelyMVC-TextView"; + productName = "ObjectivelyMVC-TextView"; + productReference = 26EA4C5E6BBC55A50927A783 /* ObjectivelyMVC-TextView */; + productType = "com.apple.product-type.tool"; + }; HUDA00000000000000000015 /* ObjectivelyMVC-HUD */ = { isa = PBXNativeTarget; buildConfigurationList = HUDA00000000000000000014 /* Build configuration list for PBXNativeTarget "ObjectivelyMVC-HUD" */; @@ -1393,6 +1585,9 @@ CED157CC1C4BF3AC00FBA2DE /* ObjectivelyMVC */, CE0C2207304CBC82008A6A2A /* ObjectivelyMVC-Font+Bitmap */, CED40A87304B2D700090C57A /* ObjectivelyMVC-ImageAtlas */, + 49B779A4A2BCD3DCD3C3BD06 /* ObjectivelyMVC-Image */, + 563130823413BCCA09134760 /* ObjectivelyMVC-Text */, + A73BC0FDBE1118F914A440F1 /* ObjectivelyMVC-TextView */, CE55C1BC1F944C6D00D5A326 /* ObjectivelyMVC-Selector */, CE88198A1F91B0BE000D5AB7 /* ObjectivelyMVC-Style */, CE34C8951FB29F700025F231 /* ObjectivelyMVC-Stylesheet */, @@ -1433,7 +1628,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "cd $BUILT_PRODUCTS_DIR &&\n./ObjectivelyMVC-Font+Bitmap &&\n./ObjectivelyMVC-ImageAtlas &&\n./ObjectivelyMVC-Selector &&\n./ObjectivelyMVC-Style &&\n./ObjectivelyMVC-Stylesheet &&\n./ObjectivelyMVC-View\n"; + shellScript = "cd $BUILT_PRODUCTS_DIR &&\n./ObjectivelyMVC-Font+Bitmap &&\n./ObjectivelyMVC-Image &&\n./ObjectivelyMVC-ImageAtlas &&\n./ObjectivelyMVC-Selector &&\n./ObjectivelyMVC-Style &&\n./ObjectivelyMVC-Stylesheet &&\n./ObjectivelyMVC-Text &&\n./ObjectivelyMVC-TextView &&\n./ObjectivelyMVC-View\n"; }; CE92C5181FE02B5C00FDB0DF /* Preprocess Assets */ = { isa = PBXShellScriptBuildPhase; @@ -1671,6 +1866,30 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 9B8AE887D35458AE7C0EE060 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5F8A115B0069B76D5DF21618 /* Image.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 243313ABE31DC93EA936F339 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B25F30D62819F943DCBC5293 /* Text.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FE5F77E35CE051DA2EE56CCD /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DCB5AE1270D2C8B8C4E44027 /* TextView.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; HUDA00000000000000000008 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1755,11 +1974,44 @@ target = CED157CC1C4BF3AC00FBA2DE /* ObjectivelyMVC */; targetProxy = CED40A89304B2D700090C57A /* PBXContainerItemProxy */; }; + C2DC6F82A5128ED1D64A0D28 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = ObjectivelyMVC; + target = CED157CC1C4BF3AC00FBA2DE /* ObjectivelyMVC */; + targetProxy = EDC8B69B4C254E0B20983FBD /* PBXContainerItemProxy */; + }; + BA04F7324C595E256D51814C /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = ObjectivelyMVC; + target = CED157CC1C4BF3AC00FBA2DE /* ObjectivelyMVC */; + targetProxy = 6CE99E2EEC2CA9CACE23F199 /* PBXContainerItemProxy */; + }; + 10B40536A4C6ED7844DECE20 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = ObjectivelyMVC; + target = CED157CC1C4BF3AC00FBA2DE /* ObjectivelyMVC */; + targetProxy = 7EE94EDE9901ED9472A2194D /* PBXContainerItemProxy */; + }; CED40A98304B2DFA0090C57A /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = CED40A87304B2D700090C57A /* ObjectivelyMVC-ImageAtlas */; targetProxy = CED40A97304B2DFA0090C57A /* PBXContainerItemProxy */; }; + B3DD5991CDBD4582092CC4F6 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 49B779A4A2BCD3DCD3C3BD06 /* ObjectivelyMVC-Image */; + targetProxy = 53F472CDBD720DEE99451939 /* PBXContainerItemProxy */; + }; + 00E123F744F252F431ACB5AB /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 563130823413BCCA09134760 /* ObjectivelyMVC-Text */; + targetProxy = 288C55F591D0A32135B0B1D8 /* PBXContainerItemProxy */; + }; + 0850FEAA7E79310A5E7C5ED5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = A73BC0FDBE1118F914A440F1 /* ObjectivelyMVC-TextView */; + targetProxy = 86612F2306FFCE232CE1BB04 /* PBXContainerItemProxy */; + }; HUDA00000000000000000011 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = CED157CC1C4BF3AC00FBA2DE /* ObjectivelyMVC */; @@ -2136,6 +2388,39 @@ }; name = Debug; }; + F63BAEE767957C57E6DF9242 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + OTHER_LDFLAGS = ( + "$(inherited)", + "-lcheck", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + A99FE10786B92347B082A9F3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + OTHER_LDFLAGS = ( + "$(inherited)", + "-lcheck", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 9C6C61A5C7F83CC04149A29D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + OTHER_LDFLAGS = ( + "$(inherited)", + "-lcheck", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; CED40A94304B2D700090C57A /* Release */ = { isa = XCBuildConfiguration; buildSettings = { @@ -2147,6 +2432,39 @@ }; name = Release; }; + 0C2EC2CD51D9FF5799F74AB7 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + OTHER_LDFLAGS = ( + "$(inherited)", + "-lcheck", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; + 7FC537B8903F19812820D170 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + OTHER_LDFLAGS = ( + "$(inherited)", + "-lcheck", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; + 8F015166B33C65189975EF38 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + OTHER_LDFLAGS = ( + "$(inherited)", + "-lcheck", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; HUDA00000000000000000012 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -2292,6 +2610,33 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + D44656D0AEE1A511C175339A /* Build configuration list for PBXNativeTarget "ObjectivelyMVC-Image" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F63BAEE767957C57E6DF9242 /* Debug */, + 0C2EC2CD51D9FF5799F74AB7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 42DA443CE43697A0CE2778F7 /* Build configuration list for PBXNativeTarget "ObjectivelyMVC-Text" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A99FE10786B92347B082A9F3 /* Debug */, + 7FC537B8903F19812820D170 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 7935DBBB394E57382160DE99 /* Build configuration list for PBXNativeTarget "ObjectivelyMVC-TextView" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9C6C61A5C7F83CC04149A29D /* Debug */, + 8F015166B33C65189975EF38 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; HUDA00000000000000000014 /* Build configuration list for PBXNativeTarget "ObjectivelyMVC-HUD" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/ObjectivelyMVC.xcodeproj/xcshareddata/xcschemes/ObjectivelyMVC-Image.xcscheme b/ObjectivelyMVC.xcodeproj/xcshareddata/xcschemes/ObjectivelyMVC-Image.xcscheme new file mode 100644 index 00000000..f8db7f75 --- /dev/null +++ b/ObjectivelyMVC.xcodeproj/xcshareddata/xcschemes/ObjectivelyMVC-Image.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ObjectivelyMVC.xcodeproj/xcshareddata/xcschemes/ObjectivelyMVC-Text.xcscheme b/ObjectivelyMVC.xcodeproj/xcshareddata/xcschemes/ObjectivelyMVC-Text.xcscheme new file mode 100644 index 00000000..0b237172 --- /dev/null +++ b/ObjectivelyMVC.xcodeproj/xcshareddata/xcschemes/ObjectivelyMVC-Text.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ObjectivelyMVC.xcodeproj/xcshareddata/xcschemes/ObjectivelyMVC-TextView.xcscheme b/ObjectivelyMVC.xcodeproj/xcshareddata/xcschemes/ObjectivelyMVC-TextView.xcscheme new file mode 100644 index 00000000..5257adf1 --- /dev/null +++ b/ObjectivelyMVC.xcodeproj/xcshareddata/xcschemes/ObjectivelyMVC-TextView.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sources/ObjectivelyMVC/Image.c b/Sources/ObjectivelyMVC/Image.c index e0ee751b..8bffc92d 100644 --- a/Sources/ObjectivelyMVC/Image.c +++ b/Sources/ObjectivelyMVC/Image.c @@ -17,6 +17,7 @@ #include #include +#include #include diff --git a/Sources/ObjectivelyMVC/ScrollView.c b/Sources/ObjectivelyMVC/ScrollView.c index b5e7b770..417ee1f9 100644 --- a/Sources/ObjectivelyMVC/ScrollView.c +++ b/Sources/ObjectivelyMVC/ScrollView.c @@ -53,12 +53,20 @@ static void awakeWithDictionary(View *self, const Dictionary *dictionary) { ScrollView *this = (ScrollView *) self; + View *contentView = NULL; + const Inlet inlets[] = MakeInlets( + MakeInlet("contentView", InletTypeView, &contentView, NULL), MakeInlet("scrollbarVisibility", InletTypeEnum, &this->scrollBarVisibility, (ident) ScrollBarVisibilityNames), MakeInlet("step", InletTypeFloat, &this->step, NULL) ); $(self, bind, inlets, dictionary); + + if (contentView) { + $(this, setContentView, contentView); + release(contentView); + } } /** @@ -79,6 +87,13 @@ static void applyStyle(View *self, const Style *style) { $(self, setNeedsLayout); } +/** + * @see View::init(View *) + */ +static View *init(View *self) { + return (View *) $((ScrollView *) self, initWithFrame, NULL); +} + /** * @see View::layoutSubviews(View *) */ @@ -259,6 +274,7 @@ static void initialize(Class *clazz) { ((ViewInterface *) clazz->interface)->applyStyle = applyStyle; ((ViewInterface *) clazz->interface)->awakeWithDictionary = awakeWithDictionary; + ((ViewInterface *) clazz->interface)->init = init; ((ViewInterface *) clazz->interface)->layoutSubviews = layoutSubviews; ((ControlInterface *) clazz->interface)->captureEvent = captureEvent; diff --git a/Sources/ObjectivelyMVC/ScrollView.h b/Sources/ObjectivelyMVC/ScrollView.h index b92e2d57..629e3fe9 100644 --- a/Sources/ObjectivelyMVC/ScrollView.h +++ b/Sources/ObjectivelyMVC/ScrollView.h @@ -78,6 +78,8 @@ struct ScrollView { /** * @brief The content View. + * @remarks A JSON layout MAY declare it as `"contentView": { ... }`, which is the same as + * ScrollView::setContentView with the resulting View. */ View *contentView; diff --git a/Sources/ObjectivelyMVC/Select.c b/Sources/ObjectivelyMVC/Select.c index 14d9fc1f..5444aa54 100644 --- a/Sources/ObjectivelyMVC/Select.c +++ b/Sources/ObjectivelyMVC/Select.c @@ -337,19 +337,18 @@ static Option *optionWithValue(const Select *self, const ident value) { return $((Array *) self->options, find, optionWithValue_predicate, value); } -/** - * @brief ArrayEnumerator for removeAllOptions. - */ -static void removeAllOptions_enumerate(const Array *array, ident obj, ident data) { - $((Select *) data, removeOption, obj); -} - /** * @fn void Select::removeAllOptions(Select *self) * @memberof Select + * @remarks The options are removed from the last, because removing an option while the array is + * enumerated skips the option after it. */ static void removeAllOptions(Select *self) { - $((Array *) self->options, enumerate, removeAllOptions_enumerate, self); + + Option *option; + while ((option = $((Array *) self->options, lastObject))) { + $(self, removeOption, option); + } } /** diff --git a/Sources/ObjectivelyMVC/StackView.c b/Sources/ObjectivelyMVC/StackView.c index 19beb92d..3713b734 100644 --- a/Sources/ObjectivelyMVC/StackView.c +++ b/Sources/ObjectivelyMVC/StackView.c @@ -110,6 +110,17 @@ static void layoutSubviews(View *self) { } } + int numStretch = 0; + for (size_t i = 0; i < subviews->count; i++) { + const View *subview = $(subviews, objectAtIndex, i); + if (subview->stretch) { + numStretch++; + } + } + + int remaining = this->distribution == StackViewDistributionDefault && numStretch ? + max(availableSize - requestedSize, 0) : 0; + int pos = 0; const float scale = requestedSize ? availableSize / (float) requestedSize : 1.f; @@ -173,8 +184,34 @@ static void layoutSubviews(View *self) { break; } + const bool stretch = subview->stretch && remaining; + if (stretch) { + const int share = --numStretch ? remaining / (numStretch + 1) : remaining; + switch (this->axis) { + case StackViewAxisVertical: + subviewSize.h += share; + break; + case StackViewAxisHorizontal: + subviewSize.w += share; + break; + } + } + $(subview, layoutWithSize, &subviewSize); + if (stretch) { + switch (this->axis) { + case StackViewAxisVertical: + remaining -= max(subview->frame.h - sizes[i].h, 0); + subviewSize.h = subview->frame.h; + break; + case StackViewAxisHorizontal: + remaining -= max(subview->frame.w - sizes[i].w, 0); + subviewSize.w = subview->frame.w; + break; + } + } + // Align along secondary axis switch (this->axis) { diff --git a/Sources/ObjectivelyMVC/TabViewController.c b/Sources/ObjectivelyMVC/TabViewController.c index 5c9280cf..70d7ae32 100644 --- a/Sources/ObjectivelyMVC/TabViewController.c +++ b/Sources/ObjectivelyMVC/TabViewController.c @@ -50,8 +50,7 @@ static void loadView(ViewController *self) { super(ViewController, self, loadView); - $(self->view->style, addEnumAttribute, "autoresizing-mask", ViewAutoresizingNames, - ViewAutoresizingContain); + $(self->view, addClassName, "tabViewController"); TabViewController *this = (TabViewController *) self; diff --git a/Sources/ObjectivelyMVC/TextView.c b/Sources/ObjectivelyMVC/TextView.c index df1ea0ec..b8c17e06 100644 --- a/Sources/ObjectivelyMVC/TextView.c +++ b/Sources/ObjectivelyMVC/TextView.c @@ -51,6 +51,8 @@ static void dealloc(Object *self) { release(this->attributedText); + release(this->completions); + super(Object, self, dealloc); } @@ -242,6 +244,103 @@ static size_t unitLengthBefore(const char *chars, size_t len, size_t position, c return unit; } +/** + * @brief Replaces the text before the cursor with `chars`, leaving the cursor after them. + */ +static void replacePrefix(TextView *self, const char *chars) { + + const Range range = { .location = 0, .length = self->position }; + $(self->attributedText, replaceCharactersInRange, range, chars); + + self->position = strlen(chars); +} + +/** + * @brief Auto-completes the text before the cursor. + * @details The first Tab completes to the longest prefix the delegate's completions share, and + * then Tab and Shift+Tab cycle through them. A lone completion is not cycled, so that the next + * Tab asks again, e.g. for the contents of a completed directory. + * @param step 1 to cycle forward, -1 to cycle backward. + * @return True if the text was completed, false if Tab should advance the key responder. + */ +static bool autocomplete(TextView *self, int step, const ImageAtlas *icons) { + + if (self->completions) { + const size_t count = self->completions->count; + + if (step > 0) { + self->completion = self->completion == count ? 0 : (self->completion + 1) % count; + } else { + self->completion = self->completion == 0 || self->completion == count ? count - 1 : self->completion - 1; + } + + const String *completion = $(self->completions, objectAtIndex, self->completion); + replacePrefix(self, completion->chars ?: ""); + return true; + } + + if (self->delegate.completionsForPrefix == NULL) { + return false; + } + + String *prefix = $(self->attributedText, substring, (Range) { .length = self->position }); + Array *completions = self->delegate.completionsForPrefix(self, prefix->chars ?: ""); + + bool didComplete = false; + + if (completions && completions->count) { + const size_t count = completions->count; + const String *first = $(completions, objectAtIndex, 0); + + size_t len = first->length; + for (size_t i = 1; i < count; i++) { + const String *other = $(completions, objectAtIndex, i); + size_t j = 0; + while (j < len && j < other->length && first->chars[j] == other->chars[j]) { + j++; + } + len = j; + } + + // Never stop inside an escape or a UTF-8 encoded character + size_t shared = 0; + while (shared < len) { + const size_t unit = unitLengthAt(first->chars, first->length, shared, icons); + if (shared + unit > len) { + break; + } + shared += unit; + } + + if (shared > prefix->length) { + String *common = $(first, substring, (Range) { .length = shared }); + replacePrefix(self, common->chars); + release(common); + + self->completion = count; + didComplete = true; + } else if (count > 1) { + self->completion = step > 0 ? 0 : count - 1; + + const String *completion = $(completions, objectAtIndex, self->completion); + replacePrefix(self, completion->chars ?: ""); + didComplete = true; + } else if (strcmp(first->chars ?: "", prefix->chars ?: "")) { + replacePrefix(self, first->chars ?: ""); + didComplete = true; + } + + if (count > 1) { + self->completions = retain(completions); + } + } + + release(completions); + release(prefix); + + return didComplete; +} + #pragma mark - Control /** @@ -249,12 +348,14 @@ static size_t unitLengthBefore(const char *chars, size_t len, size_t position, c */ static bool captureEvent(Control *self, const SDL_Event *event) { - bool didEdit = false, didCaptureEvent = false; + bool didEdit = false, didComplete = false, didCaptureEvent = false; View *view = (View *) self; TextView *this = (TextView *) self; + const size_t position = this->position; + if (this->isEditable) { if (event->type == SDL_EVENT_TEXT_INPUT) { if ($(self, isFocused)) { @@ -278,11 +379,18 @@ static bool captureEvent(Control *self, const SDL_Event *event) { switch (event->key.key) { + case SDLK_TAB: + case SDLK_KP_TAB: + if (autocomplete(this, (event->key.mod & SDL_KMOD_SHIFT) ? -1 : 1, icons)) { + didEdit = didComplete = true; + } else { + $(view, resignKeyResponder); + } + break; + case SDLK_ESCAPE: case SDLK_KP_ENTER: case SDLK_RETURN: - case SDLK_TAB: - case SDLK_KP_TAB: $(view, resignKeyResponder); break; @@ -373,6 +481,10 @@ static bool captureEvent(Control *self, const SDL_Event *event) { } } + if (!didComplete && (didEdit || this->position != position)) { + this->completions = release(this->completions); + } + if (didEdit) { $((View *) self, setNeedsLayout); if (this->delegate.didEdit) { @@ -408,6 +520,8 @@ static void stateDidChange(Control *self) { SDL_StopTextInput(self->view.window); + this->completions = release(this->completions); + if (this->delegate.didEndEditing) { this->delegate.didEndEditing(this); } @@ -453,6 +567,8 @@ static void setAttributedText(TextView *self, const char *attributedText) { self->position = self->attributedText->length; + self->completions = release(self->completions); + $((View *) self, setNeedsLayout); } } diff --git a/Sources/ObjectivelyMVC/TextView.h b/Sources/ObjectivelyMVC/TextView.h index 29c7c0cf..4371af4c 100644 --- a/Sources/ObjectivelyMVC/TextView.h +++ b/Sources/ObjectivelyMVC/TextView.h @@ -23,6 +23,7 @@ #pragma once +#include #include #include @@ -69,6 +70,19 @@ struct TextViewDelegate { * @param textView The TextView. */ void (*didEndEditing)(TextView *textView); + + /** + * @brief Delegate callback for auto-completing the text before the cursor with Tab. + * @param textView The TextView. + * @param prefix The text before the cursor. + * @return The completions for `prefix`, in the order that Tab cycles through them, or `NULL`. + * @remarks Each completion SHOULD begin with `prefix`, as it replaces `prefix`. The TextView + * completes to the longest prefix the completions share, and then Tab and Shift+Tab cycle + * through them. The TextView releases the returned Array. If this is `NULL`, or returns + * `NULL`, an empty Array, or only `prefix` itself, Tab ends editing and advances, as it does + * for any other Control. + */ + Array *(*completionsForPrefix)(TextView *textView, const char *prefix); }; /** @@ -119,6 +133,19 @@ struct TextView { * @brief The text. */ Text *text; + + /** + * @brief The completions that Tab cycles through, or `NULL`. + * @private + */ + Array *completions; + + /** + * @brief The index of the completion before the cursor, or the count of `completions` when + * the text before the cursor is their shared prefix. + * @private + */ + size_t completion; }; /** diff --git a/Sources/ObjectivelyMVC/View.c b/Sources/ObjectivelyMVC/View.c index c1489d7a..900c5126 100644 --- a/Sources/ObjectivelyMVC/View.c +++ b/Sources/ObjectivelyMVC/View.c @@ -302,13 +302,21 @@ static void applyStyle(View *self, const Style *style) { MakeInlet("padding-bottom", InletTypeInteger, &self->padding.bottom, NULL), MakeInlet("padding-left", InletTypeInteger, &self->padding.left, NULL), MakeInlet("pointer-events", InletTypeEnum, &self->pointerEvents, (ident) ViewPointerEventsNames), + MakeInlet("stretch", InletTypeBool, &self->stretch, NULL), MakeInlet("top", InletTypeInteger, &self->frame.y, NULL), MakeInlet("visibility", InletTypeEnum, &self->visibility, (ident) ViewVisibilityNames), MakeInlet("width", InletTypeInteger, &self->frame.w, NULL) ); + const ViewVisibility visibility = self->visibility; + const bool stretch = self->stretch; + $(self, bind, inlets, style->attributes); + if ((self->visibility != visibility || self->stretch != stretch) && self->superview) { + $(self->superview, setNeedsLayout); + } + if ((self->alignment & ViewAlignmentMaskHorizontal) && $(style->attributes, objectForKeyPath, "left")) { $(self, warn, WarningTypeStyle, "`left` is overruled by the horizontal `alignment`"); @@ -1710,7 +1718,7 @@ static void resize(View *self, const SDL_Size *size) { $(self, setNeedsLayout); if (self->superview) { - if ($(self->superview, isContainer) || self->alignment & ViewAlignmentMaskAny) { + if ($(self->superview, isContainer) || self->alignment & ViewAlignmentMaskAny || self->stretch) { $(self->superview, setNeedsLayout); } } diff --git a/Sources/ObjectivelyMVC/View.h b/Sources/ObjectivelyMVC/View.h index 1f8c4118..35421ccf 100644 --- a/Sources/ObjectivelyMVC/View.h +++ b/Sources/ObjectivelyMVC/View.h @@ -424,6 +424,17 @@ struct View { */ Style *style; + /** + * @brief Whether this View takes a share of the space its StackView leaves along its axis. + * @remarks A StackView gives each subview the size of its content along its axis. The subviews + * that stretch share whatever remains of the StackView's bounds, so that one child, such as a + * page beneath a row of tabs, can fill a StackView that is itself sized by its superview. + * Outside of a StackView, this has no effect. Styled as `stretch`. + * @see StackView::layoutSubviews(View *) + * @styled + */ + bool stretch; + /** * @brief An optional Stylesheet. * @remarks If set, this Stylesheet is added to or removed from the current Theme when this diff --git a/Sources/ObjectivelyMVC/WindowController.c b/Sources/ObjectivelyMVC/WindowController.c index 8ca8e582..a11ddb12 100644 --- a/Sources/ObjectivelyMVC/WindowController.c +++ b/Sources/ObjectivelyMVC/WindowController.c @@ -26,6 +26,7 @@ #include #include "Log.h" +#include "TextView.h" #include "WindowController.h" #define _Class _WindowController @@ -350,16 +351,26 @@ static void respondToEvent(WindowController *self, const SDL_Event *event) { } if (event->type == SDL_EVENT_KEY_DOWN) { - if (event->key.key == SDLK_TAB) { - - if (event->key.mod & SDL_KMOD_SHIFT) { - keyResponder = $(self, previousKeyResponder, keyResponder); - } else { - keyResponder = $(self, nextKeyResponder, keyResponder); - } - - if (keyResponder) { - $(keyResponder, becomeKeyResponder); + if (event->key.key == SDLK_TAB || event->key.key == SDLK_KP_TAB) { + + View *current = $(self, keyResponder); + + // An editing TextView that is still the key responder kept Tab to auto-complete + const bool consumed = current && current == keyResponder && + $((Object *) current, isKindOfClass, _TextView()) && + ((TextView *) current)->isEditable && + !$((Control *) current, isDisabled); + + if (!consumed) { + if (event->key.mod & SDL_KMOD_SHIFT) { + keyResponder = $(self, previousKeyResponder, keyResponder); + } else { + keyResponder = $(self, nextKeyResponder, keyResponder); + } + + if (keyResponder) { + $(keyResponder, becomeKeyResponder); + } } } diff --git a/Tests/ObjectivelyMVC/.gitignore b/Tests/ObjectivelyMVC/.gitignore index bc0e68c8..76497708 100644 --- a/Tests/ObjectivelyMVC/.gitignore +++ b/Tests/ObjectivelyMVC/.gitignore @@ -8,4 +8,5 @@ Selector Style Stylesheet Text +TextView View diff --git a/Tests/ObjectivelyMVC/Makefile.am b/Tests/ObjectivelyMVC/Makefile.am index 64f5b349..91447c43 100644 --- a/Tests/ObjectivelyMVC/Makefile.am +++ b/Tests/ObjectivelyMVC/Makefile.am @@ -13,6 +13,7 @@ TESTS = \ Style \ Stylesheet \ Text \ + TextView \ View CFLAGS += \ diff --git a/Tests/ObjectivelyMVC/TextView.c b/Tests/ObjectivelyMVC/TextView.c new file mode 100644 index 00000000..7fe7608c --- /dev/null +++ b/Tests/ObjectivelyMVC/TextView.c @@ -0,0 +1,295 @@ +/* + * ObjectivelyMVC: Object oriented MVC framework for SDL3 and C. + * Copyright (C) 2014 Jay Dolan + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + */ + +#include +#include +#include + +#include "ObjectivelyMVC.h" + +static const char **candidates; +static int requests; +static char lastPrefix[64]; + +/** + * @brief TextViewDelegate callback answering the candidates that begin with `prefix`. + */ +static Array *completionsForPrefix(TextView *textView, const char *prefix) { + + requests++; + strncpy(lastPrefix, prefix, sizeof(lastPrefix) - 1); + + Array *completions = $$(Array, array); + + for (const char **c = candidates; *c; c++) { + if (!strncmp(*c, prefix, strlen(prefix))) { + String *completion = $$(String, stringWithCharacters, *c); + $(completions, addObject, completion); + release(completion); + } + } + + return completions; +} + +static TextView *textViewWithText(const char *text) { + + TextView *textView = $(alloc(TextView), initWithFrame, NULL); + textView->delegate.completionsForPrefix = completionsForPrefix; + + $(textView, setAttributedText, text); + + requests = 0; + memset(lastPrefix, 0, sizeof(lastPrefix)); + + return textView; +} + +static void keyDown(TextView *textView, SDL_Keycode key, SDL_Keymod mod) { + + SDL_Event event = { .type = SDL_EVENT_KEY_DOWN }; + event.key.key = key; + event.key.mod = mod; + + $((Control *) textView, captureEvent, &event); +} + +static const char *text(const TextView *textView) { + return textView->attributedText->chars ?: ""; +} + +START_TEST(tabCyclesWhenSharedPrefixDoesNotExtend) { + + candidates = (const char *[]) { "abc", "abd", "abe", "x", NULL }; + + TextView *textView = textViewWithText("ab"); + + keyDown(textView, SDLK_TAB, 0); + ck_assert_str_eq("abc", text(textView)); + + keyDown(textView, SDLK_TAB, 0); + ck_assert_str_eq("abd", text(textView)); + + keyDown(textView, SDLK_TAB, 0); + ck_assert_str_eq("abe", text(textView)); + + keyDown(textView, SDLK_TAB, 0); + ck_assert_str_eq("abc", text(textView)); + + keyDown(textView, SDLK_TAB, SDL_KMOD_SHIFT); + ck_assert_str_eq("abe", text(textView)); + + ck_assert_int_eq(1, requests); + + release(textView); + +} END_TEST + +START_TEST(tabCompletesSharedPrefixThenCycles) { + + candidates = (const char *[]) { "abc", "abd", NULL }; + + TextView *textView = textViewWithText("a"); + + keyDown(textView, SDLK_TAB, 0); + ck_assert_str_eq("ab", text(textView)); + + keyDown(textView, SDLK_TAB, 0); + ck_assert_str_eq("abc", text(textView)); + + keyDown(textView, SDLK_TAB, 0); + ck_assert_str_eq("abd", text(textView)); + + release(textView); + + textView = textViewWithText("a"); + + keyDown(textView, SDLK_TAB, 0); + ck_assert_str_eq("ab", text(textView)); + + keyDown(textView, SDLK_TAB, SDL_KMOD_SHIFT); + ck_assert_str_eq("abd", text(textView)); + + release(textView); + + textView = textViewWithText("ab"); + + keyDown(textView, SDLK_TAB, SDL_KMOD_SHIFT); + ck_assert_str_eq("abd", text(textView)); + + release(textView); + +} END_TEST + +START_TEST(tabCompletesBeforeCursor) { + + candidates = (const char *[]) { "abc", NULL }; + + TextView *textView = textViewWithText("a xyz"); + textView->position = 1; + + keyDown(textView, SDLK_TAB, 0); + ck_assert_str_eq("a", lastPrefix); + ck_assert_str_eq("abc xyz", text(textView)); + ck_assert_int_eq(3, textView->position); + + release(textView); + +} END_TEST + +START_TEST(tabAfterLoneCompletionRequestsAgain) { + + candidates = (const char *[]) { "dir/", NULL }; + + TextView *textView = textViewWithText("d"); + + keyDown(textView, SDLK_TAB, 0); + ck_assert_str_eq("dir/", text(textView)); + + candidates = (const char *[]) { "dir/x", "dir/y", NULL }; + + keyDown(textView, SDLK_TAB, 0); + ck_assert_str_eq("dir/", lastPrefix); + ck_assert_str_eq("dir/x", text(textView)); + ck_assert_int_eq(2, requests); + + release(textView); + +} END_TEST + +START_TEST(tabWithNothingToCompleteChangesNothing) { + + candidates = (const char *[]) { "abc", "x", NULL }; + + TextView *textView = textViewWithText("abc"); + + keyDown(textView, SDLK_TAB, 0); + ck_assert_str_eq("abc", text(textView)); + ck_assert_ptr_null(textView->completions); + + $(textView, setAttributedText, "q"); + + keyDown(textView, SDLK_TAB, 0); + ck_assert_str_eq("q", text(textView)); + + textView->delegate.completionsForPrefix = NULL; + + keyDown(textView, SDLK_TAB, 0); + ck_assert_str_eq("q", text(textView)); + ck_assert_int_eq(2, requests); + + release(textView); + +} END_TEST + +START_TEST(editingEndsCycling) { + + candidates = (const char *[]) { "abc", "abd", NULL }; + + TextView *textView = textViewWithText("ab"); + + keyDown(textView, SDLK_TAB, 0); + ck_assert_str_eq("abc", text(textView)); + + // Pressing Shift on its own must not end cycling, or Shift+Tab could never cycle backward + keyDown(textView, SDLK_LSHIFT, 0); + keyDown(textView, SDLK_TAB, SDL_KMOD_SHIFT); + ck_assert_str_eq("abd", text(textView)); + ck_assert_int_eq(1, requests); + + keyDown(textView, SDLK_BACKSPACE, 0); + ck_assert_str_eq("ab", text(textView)); + ck_assert_ptr_null(textView->completions); + + keyDown(textView, SDLK_TAB, 0); + ck_assert_str_eq("abc", text(textView)); + ck_assert_int_eq(2, requests); + + keyDown(textView, SDLK_LEFT, 0); + ck_assert_ptr_null(textView->completions); + + release(textView); + +} END_TEST + +START_TEST(sharedPrefixKeepsWholeCharacters) { + + candidates = (const char *[]) { "a\xc3\xa9", "a\xc3\xa8", NULL }; + + TextView *textView = textViewWithText(""); + + keyDown(textView, SDLK_TAB, 0); + ck_assert_str_eq("a", text(textView)); + + release(textView); + + candidates = (const char *[]) { "a^1x", "a^2x", NULL }; + + textView = textViewWithText(""); + + keyDown(textView, SDLK_TAB, 0); + ck_assert_str_eq("a", text(textView)); + + release(textView); + +} END_TEST + +START_TEST(cyclingToEmptyCompletionClearsText) { + + candidates = (const char *[]) { "", "abc", NULL }; + + TextView *textView = textViewWithText(""); + + keyDown(textView, SDLK_TAB, 0); + ck_assert_str_eq("", text(textView)); + + keyDown(textView, SDLK_TAB, 0); + ck_assert_str_eq("abc", text(textView)); + + keyDown(textView, SDLK_TAB, 0); + ck_assert_str_eq("", text(textView)); + ck_assert_int_eq(0, textView->attributedText->length); + ck_assert_int_eq(0, textView->position); + + release(textView); + +} END_TEST + +int main(int argc, char **argv) { + + TCase *tcase = tcase_create("TextView"); + tcase_add_test(tcase, tabCyclesWhenSharedPrefixDoesNotExtend); + tcase_add_test(tcase, tabCompletesSharedPrefixThenCycles); + tcase_add_test(tcase, tabCompletesBeforeCursor); + tcase_add_test(tcase, tabAfterLoneCompletionRequestsAgain); + tcase_add_test(tcase, tabWithNothingToCompleteChangesNothing); + tcase_add_test(tcase, editingEndsCycling); + tcase_add_test(tcase, sharedPrefixKeepsWholeCharacters); + tcase_add_test(tcase, cyclingToEmptyCompletionClearsText); + + Suite *suite = suite_create("TextView"); + suite_add_tcase(suite, tcase); + + SRunner *runner = srunner_create(suite); + + srunner_run_all(runner, CK_VERBOSE); + int failed = srunner_ntests_failed(runner); + + srunner_free(runner); + + return failed; +} diff --git a/Tests/ObjectivelyMVC/View.c b/Tests/ObjectivelyMVC/View.c index 14720592..3ec74f7e 100644 --- a/Tests/ObjectivelyMVC/View.c +++ b/Tests/ObjectivelyMVC/View.c @@ -355,6 +355,187 @@ START_TEST(pointerEventsPassThroughToSubviewsAndSiblings) { } END_TEST +/** + * @brief Creates a View of the given size that a StackView may resize. + */ +static View *flexibleView(int w, int h) { + + View *view = $(alloc(View), initWithFrame, &MakeRect(0, 0, w, h)); + + view->autoresizingMask = ViewAutoresizingNone; + + return view; +} + +START_TEST(stretchChildTakesRemainingHeight) { + + StackView *stackView = $(alloc(StackView), initWithFrame, NULL); + stackView->view.minSize = stackView->view.maxSize = MakeSize(100, 200); + + View *tabs = fixedView(100, 20); + View *page = flexibleView(100, 30); + page->stretch = true; + + $((View *) stackView, addSubview, tabs); + $((View *) stackView, addSubview, page); + + $((View *) stackView, layoutIfNeeded); + + ck_assert_int_eq(200, stackView->view.frame.h); + ck_assert_int_eq(20, tabs->frame.h); + ck_assert_int_eq(20, page->frame.y); + ck_assert_int_eq(180, page->frame.h); + + release(stackView); + +} END_TEST + +START_TEST(stretchChildrenShareRemainingWidth) { + + StackView *stackView = $(alloc(StackView), initWithFrame, NULL); + stackView->view.minSize = stackView->view.maxSize = MakeSize(111, 10); + stackView->axis = StackViewAxisHorizontal; + + View *a = fixedView(10, 10); + View *b = flexibleView(0, 10); + View *c = flexibleView(0, 10); + b->stretch = c->stretch = true; + + $((View *) stackView, addSubview, a); + $((View *) stackView, addSubview, b); + $((View *) stackView, addSubview, c); + + $((View *) stackView, layoutIfNeeded); + + ck_assert_int_eq(10, a->frame.w); + ck_assert_int_eq(50, b->frame.w); + ck_assert_int_eq(10, b->frame.x); + ck_assert_int_eq(51, c->frame.w); + ck_assert_int_eq(60, c->frame.x); + + release(stackView); + +} END_TEST + +START_TEST(stretchChildAtMaxSizeLeavesRemainderToNext) { + + StackView *stackView = $(alloc(StackView), initWithFrame, NULL); + stackView->view.minSize = stackView->view.maxSize = MakeSize(110, 10); + stackView->axis = StackViewAxisHorizontal; + + View *a = fixedView(10, 10); + View *b = flexibleView(0, 10); + View *c = flexibleView(0, 10); + b->stretch = c->stretch = true; + b->maxSize.w = 30; + + $((View *) stackView, addSubview, a); + $((View *) stackView, addSubview, b); + $((View *) stackView, addSubview, c); + + $((View *) stackView, layoutIfNeeded); + + ck_assert_int_eq(30, b->frame.w); + ck_assert_int_eq(10, b->frame.x); + ck_assert_int_eq(70, c->frame.w); + ck_assert_int_eq(40, c->frame.x); + + release(stackView); + +} END_TEST + +START_TEST(stretchIsIgnoredWithoutRemainingSpace) { + + StackView *stackView = $(alloc(StackView), initWithFrame, NULL); + + View *a = fixedView(40, 20); + View *b = flexibleView(40, 30); + b->stretch = true; + + $((View *) stackView, addSubview, a); + $((View *) stackView, addSubview, b); + + $((View *) stackView, layoutIfNeeded); + + ck_assert_int_eq(50, stackView->view.frame.h); + ck_assert_int_eq(30, b->frame.h); + + release(stackView); + +} END_TEST + +START_TEST(stretchContainChildKeepsStretchedSize) { + + StackView *stackView = $(alloc(StackView), initWithFrame, NULL); + stackView->view.minSize = stackView->view.maxSize = MakeSize(100, 200); + + View *tabs = fixedView(100, 20); + + StackView *page = $(alloc(StackView), initWithFrame, NULL); + page->view.stretch = true; + $((View *) page, addSubview, fixedView(80, 40)); + + $((View *) stackView, addSubview, tabs); + $((View *) stackView, addSubview, (View *) page); + + $((View *) stackView, layoutIfNeeded); + + ck_assert_int_eq(180, page->view.frame.h); + + $((View *) page, setNeedsLayout); + $((View *) stackView, layoutIfNeeded); + + ck_assert_int_eq(180, page->view.frame.h); + + release(stackView); + +} END_TEST + +START_TEST(styledVisibilityRelaysOutSuperview) { + + StackView *stackView = $(alloc(StackView), initWithFrame, NULL); + + View *a = fixedView(40, 20); + View *b = fixedView(40, 30); + b->visibility = ViewVisibilityHidden; + + $((View *) stackView, addSubview, a); + $((View *) stackView, addSubview, b); + + $((View *) stackView, layoutIfNeeded); + + ck_assert_int_eq(20, stackView->view.frame.h); + + Style *style = $(alloc(Style), initWithAttributes, NULL); + $(style, addEnumAttribute, "visibility", ViewVisibilityNames, ViewVisibilityVisible); + $(b, applyStyle, style); + release(style); + + $((View *) stackView, layoutIfNeeded); + + ck_assert_int_eq(50, stackView->view.frame.h); + ck_assert_int_eq(20, b->frame.y); + + release(stackView); + +} END_TEST + +START_TEST(scrollViewContentViewFromJSON) { + + View *view = $$(View, viewWithCharacters, + "{ \"class\": \"ScrollView\", \"contentView\": { \"identifier\": \"content\" } }", NULL); + ck_assert_ptr_nonnull(view); + + ScrollView *scrollView = (ScrollView *) view; + ck_assert_ptr_nonnull(scrollView->contentView); + ck_assert_str_eq("content", scrollView->contentView->identifier); + ck_assert_ptr_eq(view, scrollView->contentView->superview); + ck_assert($(scrollView->contentView, hasClassName, "contentView")); + + release(view); + +} END_TEST + int main(int argc, char **argv) { TCase *tcase = tcase_create("View"); @@ -369,6 +550,13 @@ int main(int argc, char **argv) { tcase_add_test(tcase, setVisibilityMarksSuperviewNeedsLayout); tcase_add_test(tcase, alignedContainChildRecentersOnGrowth); tcase_add_test(tcase, pointerEventsPassThroughToSubviewsAndSiblings); + tcase_add_test(tcase, stretchChildTakesRemainingHeight); + tcase_add_test(tcase, stretchChildrenShareRemainingWidth); + tcase_add_test(tcase, stretchChildAtMaxSizeLeavesRemainderToNext); + tcase_add_test(tcase, stretchIsIgnoredWithoutRemainingSpace); + tcase_add_test(tcase, stretchContainChildKeepsStretchedSize); + tcase_add_test(tcase, styledVisibilityRelaysOutSuperview); + tcase_add_test(tcase, scrollViewContentViewFromJSON); Suite *suite = suite_create("View"); suite_add_tcase(suite, tcase);