diff --git a/client/ts/src/schematic/symbol/types.gen.ts b/client/ts/src/schematic/symbol/types.gen.ts index 0634e02ca2..63ff0d99c7 100644 --- a/client/ts/src/schematic/symbol/types.gen.ts +++ b/client/ts/src/schematic/symbol/types.gen.ts @@ -43,15 +43,6 @@ export const handleZ = z.object({ }); export interface Handle extends z.infer {} -/** Viewport is the camera state for viewing or previewing a symbol. */ -export const viewportZ = z.object({ - /** zoom is the zoom level where 1.0 equals 100%. */ - zoom: z.number().default(1), - /** position is the (x, y) pan offset. */ - position: spatial.xyZ, -}); -export interface Viewport extends z.infer {} - export const keyZ = z.uuid(); export type Key = z.infer; @@ -84,7 +75,7 @@ export const specZ = z.object({ /** scaleStroke indicates whether stroke width scales with the symbol size. */ scaleStroke: z.boolean().default(false), /** previewViewport is an optional viewport configuration for symbol preview rendering. */ - previewViewport: viewportZ.optional(), + previewViewport: spatial.viewportZ.optional(), }); export interface Spec extends z.infer {} diff --git a/console/src/lineplot/types/v0.ts b/console/src/lineplot/types/v0.ts index 572a1268b1..2999fa75de 100644 --- a/console/src/lineplot/types/v0.ts +++ b/console/src/lineplot/types/v0.ts @@ -43,7 +43,7 @@ export const axisStateZ = z.object({ key: axisKeyZ, label: z.string(), labelDirection: direction.directionZ, - bounds: bounds.boundsZ, + bounds: bounds.boundsZ(), autoBounds: z.object({ lower: z.boolean(), upper: z.boolean() }), tickSpacing: z.number(), labelLevel: Text.levelZ, diff --git a/console/src/lineplot/types/v1.ts b/console/src/lineplot/types/v1.ts index 5d2b9b56cb..bd8787fdfc 100644 --- a/console/src/lineplot/types/v1.ts +++ b/console/src/lineplot/types/v1.ts @@ -14,7 +14,7 @@ import * as v0 from "@/lineplot/types/v0"; export const VERSION = "1.0.0"; -export const legendStateZ = v0.legendStateZ.extend({ position: sticky.xy }); +export const legendStateZ = v0.legendStateZ.extend({ position: sticky.xyZ }); export interface LegendState extends z.infer {} export const ZERO_LEGEND_STATE: LegendState = { ...v0.ZERO_LEGEND_STATE, diff --git a/console/src/schematic/types/v1.ts b/console/src/schematic/types/v1.ts index d45cfd5059..05116480d0 100644 --- a/console/src/schematic/types/v1.ts +++ b/console/src/schematic/types/v1.ts @@ -16,7 +16,7 @@ export const VERSION = "1.0.0"; export const legendStateZ = z.object({ visible: z.boolean(), - position: sticky.xy, + position: sticky.xyZ, colors: z.record(z.string(), z.string()).default({}), }); export interface LegendState extends z.infer {} diff --git a/oracle/analyzer/analyzer.go b/oracle/analyzer/analyzer.go index 2d69670b96..00a23715e7 100644 --- a/oracle/analyzer/analyzer.go +++ b/oracle/analyzer/analyzer.go @@ -188,6 +188,7 @@ func analyze(c *analysisCtx) { for _, typ := range c.table.TypesInNamespace(c.namespace) { validateExtends(c, typ) + validateTypeParams(c, typ) } } @@ -887,6 +888,44 @@ func validateExtends(c *analysisCtx, typ resolution.Type) { } } +func typeParamsOf(form resolution.TypeForm) []resolution.TypeParam { + switch f := form.(type) { + case resolution.StructForm: + return f.TypeParams + case resolution.AliasForm: + return f.TypeParams + case resolution.DistinctForm: + return f.TypeParams + } + return nil +} + +func validateTypeParams(c *analysisCtx, typ resolution.Type) { + for _, tp := range typeParamsOf(typ.Form) { + if tp.Constraint == nil { + continue + } + if tp.Constraint.Name != "numeric" { + continue + } + if tp.Default == nil { + d := diagnostics.Errorf(nil, + "type parameter %s of %s constrained by 'numeric' requires a default (e.g. = float64); the constraint cannot be expressed concretely in Go, C++, Python, or Proto", + tp.Name, typ.Name) + d.File = c.filePath + c.diag.Add(d) + continue + } + if !resolution.IsNumberPrimitive(tp.Default.Name) { + d := diagnostics.Errorf(nil, + "type parameter %s of %s constrained by 'numeric' has non-numeric default %q; default must be a number primitive (int*, uint*, float32, float64)", + tp.Name, typ.Name, tp.Default.Name) + d.File = c.filePath + c.diag.Add(d) + } + } +} + func hasCircularInheritance(typ resolution.Type, table *resolution.Table, visited set.Set[string]) bool { if visited.Contains(typ.QualifiedName) { return true diff --git a/oracle/analyzer/analyzer_test.go b/oracle/analyzer/analyzer_test.go index 4aee0a4346..c3f0ca5bc4 100644 --- a/oracle/analyzer/analyzer_test.go +++ b/oracle/analyzer/analyzer_test.go @@ -1188,6 +1188,48 @@ var _ = Describe("Analyzer", func() { Expect(form.TypeParams[0].Constraint.Name).To(Equal("comparable")) }) + It("Should resolve numeric constraint with numeric default without warnings", func(ctx SpecContext) { + source := ` + Bounds struct { + lower T + upper T + } + ` + table, diag := analyzer.AnalyzeSource(ctx, source, "test", loader) + Expect(diag.Ok()).To(BeTrue()) + + boundsType := table.MustGet("test.Bounds") + form := boundsType.Form.(resolution.StructForm) + Expect(form.TypeParams[0].Constraint).NotTo(BeNil()) + Expect(form.TypeParams[0].Constraint.Name).To(Equal("numeric")) + Expect(form.TypeParams[0].Default).NotTo(BeNil()) + Expect(form.TypeParams[0].Default.Name).To(Equal("float64")) + }) + + It("Should reject numeric constraint without a default", func(ctx SpecContext) { + source := ` + Bounds struct { + lower T + upper T + } + ` + _, diag := analyzer.AnalyzeSource(ctx, source, "test", loader) + Expect(diag.Ok()).To(BeFalse()) + Expect(diag.String()).To(ContainSubstring("requires a default")) + }) + + It("Should reject numeric constraint with non-numeric default", func(ctx SpecContext) { + source := ` + Bounds struct { + lower T + upper T + } + ` + _, diag := analyzer.AnalyzeSource(ctx, source, "test", loader) + Expect(diag.Ok()).To(BeFalse()) + Expect(diag.String()).To(ContainSubstring("non-numeric default")) + }) + It("Should parse generic struct with default type parameter", func(ctx SpecContext) { source := ` Container struct { diff --git a/oracle/plugin/go/pb/pb.go b/oracle/plugin/go/pb/pb.go index 9d6775c44d..a986e52a4e 100644 --- a/oracle/plugin/go/pb/pb.go +++ b/oracle/plugin/go/pb/pb.go @@ -410,8 +410,6 @@ func (p *Plugin) processGenericStructForTranslation( pbName = s.Name } - data.imports.AddExternal("google.golang.org/protobuf/types/known/anypb") - typeParams := make([]typeParamData, 0, len(form.TypeParams)) typeParamNames := make([]string, 0, len(form.TypeParams)) for _, tp := range resolution.NonDefaultedTypeParams(form.TypeParams) { @@ -419,6 +417,13 @@ func (p *Plugin) processGenericStructForTranslation( typeParamNames = append(typeParamNames, tp.Name) } + // anypb is only referenced from translator signatures of structs whose + // generics survive default-substitution; for fully-defaulted generics the + // emitted translator is concrete and the import would be unused. + if len(typeParamNames) > 0 { + data.imports.AddExternal("google.golang.org/protobuf/types/known/anypb") + } + goTypeBase := fmt.Sprintf("%s.%s", data.parentAlias, goName) goTypeWithParams := goTypeBase if len(typeParamNames) > 0 { @@ -523,7 +528,7 @@ func (p *Plugin) processGenericFieldForTranslation( BackwardExpr: backwardExpr, BackwardCast: backwardCast, IsOptional: isOptional, - IsOptionalStruct: isOptional && isStructType(typeRef, data.table), + IsOptionalStruct: isHardOptional && isStructType(typeRef, data.table), HasError: hasError, HasBackwardError: hasBackwardError, }, false diff --git a/oracle/plugin/go/pb/pb_test.go b/oracle/plugin/go/pb/pb_test.go index ea6ec5eb51..604d53e089 100644 --- a/oracle/plugin/go/pb/pb_test.go +++ b/oracle/plugin/go/pb/pb_test.go @@ -1256,6 +1256,48 @@ var _ = Describe("Go PB Plugin", func() { ExpectContent(resp, "translator.gen.go"). ToContain("Name: r.Name") }) + + It("Should round-trip a soft optional struct as a non-nullable wire field", func(ctx SpecContext) { + // A struct field with a single "?" keeps its Go type as a + // value, and the proto field is plain (no `optional` keyword). + // The translator converts unconditionally in both directions: + // no zero-value guard on the Go side, no nil-check carve-out + // on the proto side. AnchorFromPB's own pb == nil guard makes + // the unconditional FromPB call safe even when the proto + // pointer is unset. Enum translators tolerate the Go zero, so + // converting a zero-valued Anchor does not error. + source := ` + @go output "core/test" + @pb + + Side enum { + left = "left" + right = "right" + } + + Anchor struct { + side Side + } + + Test struct { + key uuid + anchor Anchor? + } + ` + resp := MustGenerate(ctx, source, "test", loader, pbPlugin) + + ExpectContent(resp, "translator.gen.go"). + ToContain( + "anchorVal, err := AnchorToPB(r.Anchor)", + "Anchor: anchorVal", + "r.Anchor, err = AnchorFromPB(pb.Anchor)", + ). + ToNotContain( + "if r.Anchor != (test.Anchor{}) {", + "if pb.Anchor != nil {", + ) + }) + }) Context("cross-namespace struct reference", func() { diff --git a/oracle/plugin/go/types/types_test.go b/oracle/plugin/go/types/types_test.go index c9fe7c8484..27af831d2f 100644 --- a/oracle/plugin/go/types/types_test.go +++ b/oracle/plugin/go/types/types_test.go @@ -1274,9 +1274,12 @@ var _ = Describe("Go Types Plugin", func() { }) Context("regression tests", func() { - It("Should use snake_case for JSON struct tags regardless of field name casing", func(ctx SpecContext) { - // Regression test: JSON tags should always be snake_case, regardless of - // whether the field name is PascalCase, camelCase, or SCREAMING_CASE. + It("Should emit snake_case JSON struct tags regardless of schema field casing", func(ctx SpecContext) { + // The wire format is canonically snake_case across every + // language. Schema field names are routed through SnakeCase + // regardless of how they're spelled in the schema. TypeScript + // converts at the boundary via x/ts caseconv, so it can use + // camelCase locally without changing the wire format. source := ` @go output "arc/go/compiler" @@ -1286,6 +1289,9 @@ var _ = Describe("Go Types Plugin", func() { camelCaseField string PascalCaseField int32 already_snake_case bool + clientX float64 + signedWidth float64 + targetKey string } ` table, diag := analyzer.AnalyzeSource(ctx, source, "compiler", loader) @@ -1299,12 +1305,14 @@ var _ = Describe("Go Types Plugin", func() { Expect(err).To(BeNil()) content := string(resp.Files[0].Content) - // All JSON tags should be snake_case Expect(content).To(ContainSubstring(`json:"wasm"`)) Expect(content).To(ContainSubstring(`json:"output_memory_bases"`)) Expect(content).To(ContainSubstring(`json:"camel_case_field"`)) Expect(content).To(ContainSubstring(`json:"pascal_case_field"`)) Expect(content).To(ContainSubstring(`json:"already_snake_case"`)) + Expect(content).To(ContainSubstring(`json:"client_x"`)) + Expect(content).To(ContainSubstring(`json:"signed_width"`)) + Expect(content).To(ContainSubstring(`json:"target_key"`)) }) It("Should use alias type name in struct fields instead of expanded target", func(ctx SpecContext) { diff --git a/oracle/plugin/ts/types/types.go b/oracle/plugin/ts/types/types.go index af6d598fcf..819257f881 100644 --- a/oracle/plugin/ts/types/types.go +++ b/oracle/plugin/ts/types/types.go @@ -380,7 +380,7 @@ func (p *Plugin) extractOntology( if data == nil { return nil } - keyType := lo.Capitalize(lo.CamelCase(data.KeyField.Name)) + keyType := lo.Capitalize(camelCase(data.KeyField.Name)) primitive := data.KeyField.Primitive if override := findFieldTypeOverride(structs, data.KeyField.Name, "ts"); override != "" { primitive = override @@ -661,6 +661,12 @@ func (p *Plugin) processStruct(entry resolution.Type, table *resolution.Table, d break } } + for _, tp := range sd.TypeParams { + if tp.IsPrimitiveConstrained { + addXImport(data, xImport{name: "numeric", submodule: "numeric"}) + break + } + } sd.AllParamsOptional = true for _, tp := range sd.TypeParams { @@ -670,6 +676,16 @@ func (p *Plugin) processStruct(entry resolution.Type, table *resolution.Table, d } } + if sd.IsGeneric { + sd.IsPrimitiveConstrainedGeneric = true + for _, tp := range sd.TypeParams { + if !tp.IsPrimitiveConstrained || !tp.HasDefault { + sd.IsPrimitiveConstrainedGeneric = false + break + } + } + } + if len(form.Extends) > 0 { // Collect all parent schema names for merge chaining var allParentsValid = true @@ -686,7 +702,7 @@ func (p *Plugin) processStruct(entry resolution.Type, table *resolution.Table, d } parentTSName := domain.GetName(parentType, "ts") - schemaName := lo.CamelCase(parentTSName) + "Z" + schemaName := camelCase(parentTSName) + "Z" if parentType.Namespace != data.Namespace { ns := parentType.Namespace @@ -706,7 +722,7 @@ func (p *Plugin) processStruct(entry resolution.Type, table *resolution.Table, d if parentForm.IsGeneric() { parentInfo.IsGeneric = true for _, tp := range parentForm.TypeParams { - parentInfo.SchemaArgs = append(parentInfo.SchemaArgs, lo.CamelCase(tp.Name)) + parentInfo.SchemaArgs = append(parentInfo.SchemaArgs, camelCase(tp.Name)) } } @@ -721,7 +737,7 @@ func (p *Plugin) processStruct(entry resolution.Type, table *resolution.Table, d sd.ExtendsParentSchemaArgs = sd.ExtendsParents[0].SchemaArgs for _, f := range form.OmittedFields { - sd.OmittedFields = append(sd.OmittedFields, lo.CamelCase(f)) + sd.OmittedFields = append(sd.OmittedFields, camelCase(f)) } parentFields := make(map[string]resolution.Field) @@ -742,7 +758,7 @@ func (p *Plugin) processStruct(entry resolution.Type, table *resolution.Table, d } else if isOnlyOptionalityChange(parentField, field) { sd.PartialFields = append(sd.PartialFields, p.processField(field, entry, table, data, sd.UseInput, sd.ConcreteTypes)) } else { - sd.OmittedFields = append(sd.OmittedFields, lo.CamelCase(field.Name)) + sd.OmittedFields = append(sd.OmittedFields, camelCase(field.Name)) sd.ExtendFields = append(sd.ExtendFields, p.processField(field, entry, table, data, sd.UseInput, sd.ConcreteTypes)) } } else { @@ -871,6 +887,38 @@ func computeCoalescedTypes(sd *structData) { } } +// camelCase converts a name to camelCase, preserving trailing acronym runs of +// two or more uppercase letters in the source. For example "ClientXY" becomes +// "clientXY" rather than "clientXy", and "EntityID" becomes "entityID". When +// the entire input is uppercase ("XY", "URL"), the standard all-lowercase +// camelCase result is returned ("xy", "url"). Inputs containing underscores or +// other non-alpha separators are routed through lo.CamelCase first so existing +// snake_case/kebab-case handling is preserved. +func camelCase(s string) string { + if s == "" { + return s + } + base := lo.CamelCase(s) + end := len(s) + runStart := end + for runStart > 0 { + c := s[runStart-1] + if c >= 'A' && c <= 'Z' { + runStart-- + continue + } + break + } + runLen := end - runStart + if runLen < 2 || runStart == 0 { + return base + } + if len(base) < runLen { + return base + } + return base[:len(base)-runLen] + s[runStart:] +} + func coalesceTSType(tsType string, typeParams []typeParamData) string { sorted := make([]typeParamData, len(typeParams)) copy(sorted, typeParams) @@ -879,7 +927,7 @@ func coalesceTSType(tsType string, typeParams []typeParamData) string { }) result := tsType for _, tp := range sorted { - result = strings.ReplaceAll(result, tp.Name, `S["`+lo.CamelCase(tp.Name)+`"]`) + result = strings.ReplaceAll(result, tp.Name, `S["`+camelCase(tp.Name)+`"]`) } return result } @@ -894,10 +942,14 @@ func (p *Plugin) processTypeParam(tp resolution.TypeParam, table *resolution.Tab if resolution.IsPrimitive(tp.Constraint.Name) && tp.Constraint.Name == "string" { tpd.Constraint = "z.ZodType" } + if tp.Constraint.Name == "numeric" { + tpd.IsPrimitiveConstrained = true + tpd.BareConstraint = "numeric.Value" + } resolved, ok := tp.Constraint.Resolve(table) if ok { if _, isEnum := resolved.Form.(resolution.EnumForm); isEnum { - enumTypeName := lo.Capitalize(lo.CamelCase(resolved.Name)) + enumTypeName := lo.Capitalize(camelCase(resolved.Name)) tpd.Constraint = "z.ZodType<" + enumTypeName + ">" } } @@ -912,7 +964,7 @@ func (p *Plugin) processTypeParam(tp resolution.TypeParam, table *resolution.Tab resolved, ok := tp.Default.Resolve(table) if ok { if _, isEnum := resolved.Form.(resolution.EnumForm); isEnum { - enumZodName := lo.CamelCase(resolved.Name) + "Z" + enumZodName := camelCase(resolved.Name) + "Z" tpd.Default = "typeof " + enumZodName tpd.DefaultValue = enumZodName } else { @@ -923,10 +975,31 @@ func (p *Plugin) processTypeParam(tp resolution.TypeParam, table *resolution.Tab tpd.Default = defaultToTS(tp.Default.Name) tpd.DefaultValue = defaultValueToTS(tp.Default.Name) } + if tpd.IsPrimitiveConstrained { + tpd.BareDefault = primitiveToBareTS(tp.Default.Name) + } } return tpd } +// primitiveToBareTS maps an Oracle primitive name to its bare TypeScript type +// (e.g. number, bigint, string, boolean) for use in interface signatures of +// primitive-constrained generics. Returns the empty string for primitives with +// no bare-TS mapping. +func primitiveToBareTS(name string) string { + switch name { + case "string", "uuid": + return "string" + case "bool": + return "boolean" + case "int8", "int16", "int32", "int64", + "uint8", "uint12", "uint16", "uint20", "uint32", "uint64", + "float32", "float64": + return "number" + } + return "" +} + type typeParamMapping struct { zodType string zodValue string @@ -973,7 +1046,7 @@ func fallbackForConstraint(constraint *resolution.TypeRef, table *resolution.Tab resolved, ok := constraint.Resolve(table) if ok { if _, isEnum := resolved.Form.(resolution.EnumForm); isEnum { - return lo.CamelCase(resolved.Name) + "Z" + return camelCase(resolved.Name) + "Z" } } return defaultValueToTS(constraint.Name) @@ -986,7 +1059,7 @@ func fallbackSchemaTypeForConstraint(constraint *resolution.TypeRef, table *reso resolved, ok := constraint.Resolve(table) if ok { if _, isEnum := resolved.Form.(resolution.EnumForm); isEnum { - return "typeof " + lo.CamelCase(resolved.Name) + "Z" + return "typeof " + camelCase(resolved.Name) + "Z" } } if m, ok := typeParamMappings[constraint.Name]; ok { @@ -1058,7 +1131,7 @@ func (p *Plugin) processField(field resolution.Field, parentType resolution.Type fd := fieldData{ Name: field.Name, - TSName: lo.CamelCase(field.Name), + TSName: camelCase(field.Name), Doc: doc.Get(field.Domains), IsOptional: field.IsOptional, IsHardOptional: field.IsHardOptional, @@ -1228,7 +1301,7 @@ func (p *Plugin) typeArgToTSType(typeRef *resolution.TypeRef, table *resolution. switch form := resolved.Form.(type) { case resolution.StructForm: - schemaName := lo.CamelCase(resolved.Name) + "Z" + schemaName := camelCase(resolved.Name) + "Z" if form.IsGeneric() && len(typeRef.TypeArgs) > 0 { // For generic types with args, recursively get the full schema call args := make([]string, len(typeRef.TypeArgs)) @@ -1240,7 +1313,7 @@ func (p *Plugin) typeArgToTSType(typeRef *resolution.TypeRef, table *resolution. } else { namedArgs := make([]string, len(typeRef.TypeArgs)) for i, arg := range args { - namedArgs[i] = fmt.Sprintf("%s: %s", lo.CamelCase(form.TypeParams[i].Name), arg) + namedArgs[i] = fmt.Sprintf("%s: %s", camelCase(form.TypeParams[i].Name), arg) } schemaName = fmt.Sprintf("%s({%s})", schemaName, strings.Join(namedArgs, ", ")) } @@ -1257,7 +1330,7 @@ func (p *Plugin) typeArgToTSType(typeRef *resolution.TypeRef, table *resolution. return fmt.Sprintf("typeof %s", schemaName) case resolution.EnumForm: - schemaName := lo.CamelCase(resolved.Name) + "Z" + schemaName := camelCase(resolved.Name) + "Z" if resolved.Namespace != data.Namespace { ns := resolved.Namespace targetOutputPath := enum.FindOutputPath(resolved, table, "ts") @@ -1295,10 +1368,13 @@ func (p *Plugin) typeRefToZodInternal(typeRef *resolution.TypeRef, table *resolu return "z.unknown()" } if typeRef.IsTypeParam() && typeRef.TypeParam != nil { - paramName := lo.CamelCase(typeRef.TypeParam.Name) + paramName := camelCase(typeRef.TypeParam.Name) if forStructArg { return paramName } + if typeRef.TypeParam.Constraint != nil && typeRef.TypeParam.Constraint.Name == "numeric" { + return fmt.Sprintf("%s ?? %s", paramName, defaultValueToTS(typeRef.TypeParam.Default.Name)) + } if typeRef.TypeParam.Default != nil || typeRef.TypeParam.Optional { effectiveRef := typeRef.TypeParam.Constraint if effectiveRef == nil && typeRef.TypeParam.Default != nil && !typeRef.TypeParam.Optional { @@ -1337,7 +1413,7 @@ func (p *Plugin) typeRefToZodInternal(typeRef *resolution.TypeRef, table *resolu switch form := resolved.Form.(type) { case resolution.StructForm: - schemaName := lo.CamelCase(domain.GetName(resolved, "ts")) + "Z" + schemaName := camelCase(domain.GetName(resolved, "ts")) + "Z" if form.IsGeneric() { nonNilArgs := make([]struct { index int @@ -1358,7 +1434,7 @@ func (p *Plugin) typeRefToZodInternal(typeRef *resolution.TypeRef, table *resolu } else { namedArgs := make([]string, len(nonNilArgs)) for i, arg := range nonNilArgs { - namedArgs[i] = fmt.Sprintf("%s: %s", lo.CamelCase(form.TypeParams[arg.index].Name), arg.value) + namedArgs[i] = fmt.Sprintf("%s: %s", camelCase(form.TypeParams[arg.index].Name), arg.value) } schemaName = fmt.Sprintf("%s({%s})", schemaName, strings.Join(namedArgs, ", ")) } @@ -1378,7 +1454,7 @@ func (p *Plugin) typeRefToZodInternal(typeRef *resolution.TypeRef, table *resolu return schemaName case resolution.EnumForm: - enumName := lo.CamelCase(domain.GetName(resolved, "ts")) + "Z" + enumName := camelCase(domain.GetName(resolved, "ts")) + "Z" if resolved.Namespace != data.Namespace { ns := resolved.Namespace targetOutputPath := enum.FindOutputPath(resolved, table, "ts") @@ -1391,7 +1467,7 @@ func (p *Plugin) typeRefToZodInternal(typeRef *resolution.TypeRef, table *resolu return enumName case resolution.DistinctForm: - schemaName := lo.CamelCase(domain.GetName(resolved, "ts")) + "Z" + schemaName := camelCase(domain.GetName(resolved, "ts")) + "Z" if resolved.Namespace != data.Namespace { ns := resolved.Namespace targetOutputPath := output.GetPath(resolved, "ts") @@ -1405,7 +1481,7 @@ func (p *Plugin) typeRefToZodInternal(typeRef *resolution.TypeRef, table *resolu case resolution.AliasForm: if !form.IsGeneric() { - schemaName := lo.CamelCase(domain.GetName(resolved, "ts")) + "Z" + schemaName := camelCase(domain.GetName(resolved, "ts")) + "Z" if resolved.Namespace != data.Namespace { ns := resolved.Namespace targetOutputPath := output.GetPath(resolved, "ts") @@ -1446,6 +1522,9 @@ func (p *Plugin) typeRefToTSInternal(typeRef *resolution.TypeRef, table *resolut if forStructArg { return typeRef.TypeParam.Name } + if typeRef.TypeParam.Constraint != nil && typeRef.TypeParam.Constraint.Name == "numeric" { + return typeRef.TypeParam.Name + } return fmt.Sprintf("z.infer<%s>", typeRef.TypeParam.Name) } if resolution.IsPrimitive(typeRef.Name) { @@ -1685,15 +1764,15 @@ func (p *Plugin) typeRefToZodSchemaType(typeRef *resolution.TypeRef, table *reso for i, arg := range typeRef.TypeArgs { args[i] = p.typeRefToZodSchemaType(&arg, table, data) } - return fmt.Sprintf("ReturnType>", prefix, lo.CamelCase(tsName), strings.Join(args, ", ")) + return fmt.Sprintf("ReturnType>", prefix, camelCase(tsName), strings.Join(args, ", ")) } - return fmt.Sprintf("typeof %s%sZ", prefix, lo.CamelCase(tsName)) + return fmt.Sprintf("typeof %s%sZ", prefix, camelCase(tsName)) case resolution.EnumForm: - return fmt.Sprintf("typeof %s%sZ", prefix, lo.CamelCase(tsName)) + return fmt.Sprintf("typeof %s%sZ", prefix, camelCase(tsName)) case resolution.DistinctForm: - return fmt.Sprintf("typeof %s%sZ", prefix, lo.CamelCase(tsName)) + return fmt.Sprintf("typeof %s%sZ", prefix, camelCase(tsName)) } return "z.ZodType" @@ -1950,6 +2029,12 @@ type structData struct { IsGeneric bool ConcreteTypes bool CoalesceTypeParams bool + // IsPrimitiveConstrainedGeneric is true when every type param is constrained + // to a primitive set (e.g. T extends numeric) and has a default. In this mode + // the runtime zod schema is emitted as a plain z.object (defaults substituted + // for type-param-typed fields), but the TS interface keeps the generic shape + // with primitive constraints (e.g. T extends number | bigint = number). + IsPrimitiveConstrainedGeneric bool } type extendsParentInfo struct { @@ -1961,7 +2046,14 @@ type extendsParentInfo struct { type typeParamData struct { Name, Constraint, Default, DefaultValue string - HasDefault, IsJSON bool + // BareConstraint and BareDefault are populated for primitive-constrained + // generics (e.g. T extends numeric). Constraint/Default carry the zod-side + // values used in runtime-builder positions; BareConstraint/BareDefault carry + // the TS-side values used in interface signatures (e.g. "number | bigint", + // "number"). Empty for non-primitive-constrained params. + BareConstraint, BareDefault string + HasDefault, IsJSON bool + IsPrimitiveConstrained bool } type fieldData struct { @@ -1992,7 +2084,7 @@ type enumValueData struct { } var templateFuncs = template.FuncMap{ - "camelCase": lo.CamelCase, + "camelCase": camelCase, "title": lo.Capitalize, "lower": strings.ToLower, "pluralUpper": func(name string) string { return strings.ToUpper(lo.SnakeCase(name)) + "S" }, @@ -2090,6 +2182,27 @@ export interface {{ .TSName }} extends z.{{ if .UseInput }}input{{ else }}infer{ {{- end }} {{- end }} {{- end }} +{{- else if .IsPrimitiveConstrainedGeneric }} +{{- if .Doc }} + +{{ formatDoc .TSName .Doc }} +{{- end }} +export const {{ camelCase .TSName }}Z = <{{ range $i, $p := .TypeParams }}{{ if $i }}, {{ end }}{{ $p.Name }} extends {{ $p.BareConstraint }}{{ if $p.HasDefault }} = {{ $p.BareDefault }}{{ end }}{{ end }}>({{ range $i, $p := .TypeParams }}{{ if $i }}, {{ end }}{{ $p.Name | camelCase }}?: z.ZodType<{{ $p.Name }}>{{ end }}) => + z.object({ +{{- range .Fields }} +{{- if .Doc }} + {{ formatDoc .TSName .Doc }} +{{- end }} + {{ .TSName }}: {{ .ZodType }}, +{{- end }} + }); +{{- if $.GenerateTypes }} +export interface {{ .TSName }}<{{ range $i, $p := .TypeParams }}{{ if $i }}, {{ end }}{{ $p.Name }} extends {{ $p.BareConstraint }}{{ if $p.HasDefault }} = {{ $p.BareDefault }}{{ end }}{{ end }}> { +{{- range .Fields }} + {{ .TSName }}{{ if or .IsOptional .IsHardOptional }}?{{ end }}: {{ .TSType }}{{ if .IsArray }}[]{{ end }}; +{{- end }} +} +{{- end }} {{- else if .IsGeneric }} {{- if .IsSingleParam }} {{- if and .ConcreteTypes .ConditionalFields }} diff --git a/oracle/plugin/ts/types/types_test.go b/oracle/plugin/ts/types/types_test.go index 2df4d1beb3..887d120acf 100644 --- a/oracle/plugin/ts/types/types_test.go +++ b/oracle/plugin/ts/types/types_test.go @@ -1021,6 +1021,76 @@ var _ = Describe("TS Types Plugin", func() { Expect(content).To(ContainSubstring(`name: z.string()`)) }) + It("Should preserve trailing acronyms in generated zod schema names", func(ctx SpecContext) { + source := ` + @ts output "out" + + ClientXY struct { + clientX float64 + clientY float64 + } + + StickyXY struct { + x float64 + y float64 + } + + EntityID struct { + value string + } + ` + table, diag := analyzer.AnalyzeSource(ctx, source, "test", loader) + Expect(diag.Ok()).To(BeTrue()) + + req := &plugin.Request{ + Resolutions: table, + } + + resp, err := typesPlugin.Generate(req) + Expect(err).To(BeNil()) + + content := string(resp.Files[0].Content) + Expect(content).To(ContainSubstring(`export const clientXYZ = z.object({`)) + Expect(content).To(ContainSubstring(`export const stickyXYZ = z.object({`)) + Expect(content).To(ContainSubstring(`export const entityIDZ = z.object({`)) + Expect(content).To(ContainSubstring(`typeof clientXYZ`)) + Expect(content).To(ContainSubstring(`typeof stickyXYZ`)) + Expect(content).NotTo(ContainSubstring(`clientXyZ`)) + Expect(content).NotTo(ContainSubstring(`stickyXyZ`)) + Expect(content).NotTo(ContainSubstring(`entityIdZ`)) + }) + + It("Should emit numeric-constrained generic as function with value-typed generic interface", func(ctx SpecContext) { + source := ` + @ts output "out" + + Bounds struct { + lower T + upper T + } + ` + table, diag := analyzer.AnalyzeSource(ctx, source, "test", loader) + Expect(diag.Ok()).To(BeTrue()) + + req := &plugin.Request{ + Resolutions: table, + } + + resp, err := typesPlugin.Generate(req) + Expect(err).To(BeNil()) + + content := string(resp.Files[0].Content) + Expect(content).To(ContainSubstring(`import { numeric } from "@synnaxlabs/x"`)) + Expect(content).To(ContainSubstring(`export const boundsZ = (t?: z.ZodType) =>`)) + Expect(content).To(ContainSubstring(`lower: t ?? z.number()`)) + Expect(content).To(ContainSubstring(`upper: t ?? z.number()`)) + Expect(content).To(ContainSubstring(`export interface Bounds {`)) + Expect(content).To(ContainSubstring(`lower: T;`)) + Expect(content).To(ContainSubstring(`upper: T;`)) + Expect(content).NotTo(ContainSubstring(`z.infer`)) + Expect(content).NotTo(ContainSubstring(` {} const BASE_SIZE = 24; const SUB_SIZE = 12; -const SUB_POSITIONS: Record = { +const SUB_POSITIONS: Record = { topRight: { x: BASE_SIZE - SUB_SIZE, y: 0 }, topLeft: { x: 0, y: 0 }, bottomLeft: { x: 0, y: BASE_SIZE - SUB_SIZE }, @@ -42,7 +42,7 @@ const SUB_POSITIONS: Record = }; const createSubIcon = ( - key: location.CornerXYString, + key: location.CornerString, Icon: FC | undefined, ): ReactElement | null => { if (Icon == null) return null; diff --git a/pluto/src/lineplot/aether/YAxis.ts b/pluto/src/lineplot/aether/YAxis.ts index 3eba7aa172..577ee3b2d8 100644 --- a/pluto/src/lineplot/aether/YAxis.ts +++ b/pluto/src/lineplot/aether/YAxis.ts @@ -14,7 +14,7 @@ import { line } from "@/vis/line/aether"; import { rule } from "@/vis/rule/aether"; export const yAxisStateZ = baseAxisStateZ.extend({ - location: location.x.default("left"), + location: location.xZ.default("left"), }); export interface YAxisProps extends AxisRenderProps { diff --git a/pluto/src/lineplot/aether/axis.ts b/pluto/src/lineplot/aether/axis.ts index 754d72d8d0..43ab90f899 100644 --- a/pluto/src/lineplot/aether/axis.ts +++ b/pluto/src/lineplot/aether/axis.ts @@ -29,7 +29,7 @@ import { render } from "@/vis/render"; export const baseAxisStateZ = axis.axisStateZ .extend({ axisKey: z.string().optional(), - bounds: bounds.boundsZ.optional(), + bounds: bounds.boundsZ().optional(), autoBounds: z .object({ lower: z.boolean().default(true), diff --git a/pluto/src/lineplot/range/aether/provider.ts b/pluto/src/lineplot/range/aether/provider.ts index 5d2d554f4a..9109c2e93a 100644 --- a/pluto/src/lineplot/range/aether/provider.ts +++ b/pluto/src/lineplot/range/aether/provider.ts @@ -31,7 +31,7 @@ import { Draw2D } from "@/vis/draw2d"; import { render } from "@/vis/render"; export const selectedStateZ = ranger.payloadZ.extend({ - viewport: bounds.boundsZ, + viewport: bounds.boundsZ(), }); export type SelectedState = z.infer; diff --git a/pluto/src/schematic/symbol/Primitives.tsx b/pluto/src/schematic/symbol/Primitives.tsx index 2529b01d29..761bd2937d 100644 --- a/pluto/src/schematic/symbol/Primitives.tsx +++ b/pluto/src/schematic/symbol/Primitives.tsx @@ -1405,12 +1405,12 @@ export const Filter = ({ ); -type DetailedBorderRadius = Record; +type DetailedBorderRadius = Record; type BorderRadius = | number | Record - | Record + | Record | DetailedBorderRadius; const parseBorderRadius = (radius: BorderRadius): DetailedBorderRadius => { diff --git a/pluto/src/table/cells/Cells.tsx b/pluto/src/table/cells/Cells.tsx index 118d9338db..05f5f14a8f 100644 --- a/pluto/src/table/cells/Cells.tsx +++ b/pluto/src/table/cells/Cells.tsx @@ -26,7 +26,7 @@ export const textPropsZ = z.object({ value: z.string(), level: BaseText.levelZ, weight: BaseText.weightZ, - align: location.x.or(location.center), + align: location.xZ.or(location.centerZ), backgroundColor: color.crudeZ, }); export type TextProps = z.infer; diff --git a/pluto/src/telem/aether/transformers.ts b/pluto/src/telem/aether/transformers.ts index 10fdcda5f0..64dac1b83c 100644 --- a/pluto/src/telem/aether/transformers.ts +++ b/pluto/src/telem/aether/transformers.ts @@ -89,7 +89,7 @@ export class SetPoint } } -export const withinBoundsProps = z.object({ trueBound: bounds.boundsZ }); +export const withinBoundsProps = z.object({ trueBound: bounds.boundsZ() }); export type WithinBoundsProps = z.infer; diff --git a/pluto/src/vis/draw2d/index.ts b/pluto/src/vis/draw2d/index.ts index 8b239aa0c2..e31ee2100d 100644 --- a/pluto/src/vis/draw2d/index.ts +++ b/pluto/src/vis/draw2d/index.ts @@ -101,7 +101,7 @@ export interface Draw2DTextContainerProps extends Omit, Draw2DMeasureTextContainerProps { position: xy.XY; offset?: xy.XY; - root?: location.CornerXY; + root?: location.Corner; } export interface DrawList { @@ -111,7 +111,7 @@ export interface DrawList { spacing?: number; width: number; draw: (index: number, box: box.Box) => void; - root?: location.CornerXY; + root?: location.Corner; offset?: xy.XY; padding?: xy.XY; } diff --git a/pluto/src/vis/gauge/aether/gauge.ts b/pluto/src/vis/gauge/aether/gauge.ts index 4eaf95c9e1..3b5fbf5edb 100644 --- a/pluto/src/vis/gauge/aether/gauge.ts +++ b/pluto/src/vis/gauge/aether/gauge.ts @@ -41,7 +41,7 @@ const gaugeState = z.object({ notation: notation.notationZ.default("standard"), location: location.xy.default({ x: "left", y: "center" }), units: z.string().default("RPM"), - bounds: bounds.boundsZ.default(bounds.construct(0, 100)), + bounds: bounds.boundsZ().default(bounds.construct(0, 100)), // New gauge configuration properties barWidth: z.number().default(12), // Width of the gauge bar in pixels }); diff --git a/pluto/src/vis/value/redline.ts b/pluto/src/vis/value/redline.ts index fc3e315be1..f772ca9891 100644 --- a/pluto/src/vis/value/redline.ts +++ b/pluto/src/vis/value/redline.ts @@ -10,6 +10,9 @@ import { bounds, color } from "@synnaxlabs/x"; import { z } from "zod"; -export const redlineZ = z.object({ bounds: bounds.boundsZ, gradient: color.gradientZ }); +export const redlineZ = z.object({ + bounds: bounds.boundsZ(), + gradient: color.gradientZ, +}); export type Redline = z.infer; export const ZERO_READLINE: Redline = { bounds: { lower: 0, upper: 1 }, gradient: [] }; diff --git a/schemas/spatial.oracle b/schemas/spatial.oracle index 626ca04a86..8de6e07eb9 100644 --- a/schemas/spatial.oracle +++ b/schemas/spatial.oracle @@ -37,3 +37,201 @@ XY struct { elements in two-dimensional space. """ } + +Direction enum { + x = "x" + y = "y" + + @doc value "is a 2D axis direction." +} + +XLocation enum { + left = "left" + right = "right" + + @doc value "is a horizontal-axis location at the left or right edge." +} + +YLocation enum { + top = "top" + bottom = "bottom" + + @doc value "is a vertical-axis location at the top or bottom edge." +} + +StickyUnit enum { + px = "px" + decimal = "decimal" + + @doc value """ + is the measurement unit for a sticky coordinate, either pixels or a + decimal fraction of the container. + """ +} + +CornerLocation struct { + x XLocation { + @doc value "is the horizontal anchor." + } + y YLocation { + @doc value "is the vertical anchor." + } + + @doc value "is an anchor corner for positioning." +} + +StickyUnits struct { + x StickyUnit { + @doc value "is the horizontal unit." + } + y StickyUnit { + @doc value "is the vertical unit." + } + + @doc value "specifies the measurement units for sticky positioning." +} + +StickyXY struct { + x float64 { + @doc value "is the horizontal coordinate." + } + y float64 { + @doc value "is the vertical coordinate." + } + root CornerLocation?? { + @doc value "is the optional anchor corner for the position." + } + units StickyUnits?? { + @doc value "is the optional unit specification for the coordinates." + } + + @doc value """ + is a position that can be anchored to different corners of a + container with configurable units (pixels or decimal fractions). + """ +} + +Dimensions struct { + width float64 { + @doc value "is the width in pixels." + } + height float64 { + @doc value "is the height in pixels." + } + + @doc value "is a 2D size with width and height values." +} + +Viewport struct { + zoom float64 { + @validate default 1 + @doc value "is the zoom level where 1.0 equals 100%." + } + position XY { + @doc value "is the (x, y) pan offset of the viewport." + } + + @doc value "is the camera state of a viewport." +} + +AngularDirection enum { + clockwise = "clockwise" + counterclockwise = "counterclockwise" + + @doc value "is a rotational direction in 2D space." +} + +CenterLocation enum { + center = "center" + + @doc value "is a location at the center of a container." +} + +Location enum { + top = "top" + right = "right" + bottom = "bottom" + left = "left" + center = "center" + + @doc value """ + is a position indicator covering the four outer edges of a container + and its center. + """ +} + +Alignment enum { + start = "start" + center = "center" + end = "end" + + @doc value """ + is a positioning indicator for aligning content along an axis within a + container. + """ +} + +Order enum { + first = "first" + last = "last" + + @doc value "is a positional ordering indicator for elements in a sequence." +} + +Dimension enum { + width = "width" + height = "height" + + @doc value "is the name of a 2D size axis." +} + +SignedDimension enum { + signedWidth = "signedWidth" + signedHeight = "signedHeight" + + @doc value "is the name of a 2D signed size axis." +} + +SignedDimensions struct { + signedWidth float64 { + @doc value "is the signed width." + } + signedHeight float64 { + @doc value "is the signed height." + } + + @doc value """ + is a 2D size whose width and height components carry sign, allowing + negative values to express direction. + """ +} + +ClientXY struct { + clientX float64 { + @doc value "is the horizontal coordinate in client (viewport) space." + } + clientY float64 { + @doc value "is the vertical coordinate in client (viewport) space." + } + + @doc value """ + is a 2D coordinate point expressed in client (viewport) space, matching + the shape of DOM mouse events. + """ +} + +Bounds struct { + lower T { + @doc value "is the inclusive lower bound." + } + upper T { + @doc value "is the exclusive upper bound." + } + + @doc value """ + is a closed-open interval [lower, upper) over an ordered numeric value + space. The TypeScript binding is generic over T so callers can express + bounds over either number or bigint values; other languages emit a + concrete float64-based type. + """ +} diff --git a/schemas/symbol.oracle b/schemas/symbol.oracle index 0de9666c83..fea251bc5a 100644 --- a/schemas/symbol.oracle +++ b/schemas/symbol.oracle @@ -70,42 +70,30 @@ Handle struct { """ } -Viewport struct { - zoom float64 { - @validate default 1 - @doc value "is the zoom level where 1.0 equals 100%." - } - position spatial.XY { - @doc value "is the (x, y) pan offset." - } - - @doc value "is the camera state for viewing or previewing a symbol." -} - Spec struct { - svg string { + svg string { @validate min_length 1 @doc value "is the SVG markup defining the symbol's visual geometry." } - states State[] { + states State[] { @doc value "contains available visual states with regional styling configurations." } - variant string { + variant string { @validate min_length 1 @doc value "is the symbol variant or category identifier (e.g., 'sensor', 'valve')." } - handles Handle[] { + handles Handle[] { @doc value "contains connection points for linking to other diagram elements." } - scale float64 { + scale float64 { @validate default 1 @doc value "is the symbol scale factor." } - scale_stroke bool { + scale_stroke bool { @validate default false @doc value "indicates whether stroke width scales with the symbol size." } - preview_viewport Viewport? { + preview_viewport spatial.Viewport? { @doc value "is an optional viewport configuration for symbol preview rendering." } diff --git a/x/cpp/spatial/json.gen.h b/x/cpp/spatial/json.gen.h index 67a994ea61..6a84b5cc54 100644 --- a/x/cpp/spatial/json.gen.h +++ b/x/cpp/spatial/json.gen.h @@ -11,6 +11,8 @@ #pragma once +#include + #include "x/cpp/json/json.h" #include "x/cpp/spatial/types.gen.h" @@ -30,4 +32,120 @@ inline x::json::json XY::to_json() const { return j; } +inline CornerLocation CornerLocation::parse(x::json::Parser parser) { + return CornerLocation{ + .x = parser.field("x"), + .y = parser.field("y"), + }; +} + +inline x::json::json CornerLocation::to_json() const { + x::json::json j; + j["x"] = this->x; + j["y"] = this->y; + return j; +} + +inline StickyUnits StickyUnits::parse(x::json::Parser parser) { + return StickyUnits{ + .x = parser.field("x"), + .y = parser.field("y"), + }; +} + +inline x::json::json StickyUnits::to_json() const { + x::json::json j; + j["x"] = this->x; + j["y"] = this->y; + return j; +} + +inline StickyXY StickyXY::parse(x::json::Parser parser) { + return StickyXY{ + .x = parser.field("x"), + .y = parser.field("y"), + .root = parser.field>("root"), + .units = parser.field>("units"), + }; +} + +inline x::json::json StickyXY::to_json() const { + x::json::json j; + j["x"] = this->x; + j["y"] = this->y; + if (this->root.has_value()) j["root"] = this->root->to_json(); + if (this->units.has_value()) j["units"] = this->units->to_json(); + return j; +} + +inline Dimensions Dimensions::parse(x::json::Parser parser) { + return Dimensions{ + .width = parser.field("width"), + .height = parser.field("height"), + }; +} + +inline x::json::json Dimensions::to_json() const { + x::json::json j; + j["width"] = this->width; + j["height"] = this->height; + return j; +} + +inline Viewport Viewport::parse(x::json::Parser parser) { + return Viewport{ + .zoom = parser.field("zoom"), + .position = parser.field("position"), + }; +} + +inline x::json::json Viewport::to_json() const { + x::json::json j; + j["zoom"] = this->zoom; + j["position"] = this->position.to_json(); + return j; +} + +inline SignedDimensions SignedDimensions::parse(x::json::Parser parser) { + return SignedDimensions{ + .signed_width = parser.field("signed_width"), + .signed_height = parser.field("signed_height"), + }; +} + +inline x::json::json SignedDimensions::to_json() const { + x::json::json j; + j["signed_width"] = this->signed_width; + j["signed_height"] = this->signed_height; + return j; +} + +inline ClientXY ClientXY::parse(x::json::Parser parser) { + return ClientXY{ + .client_x = parser.field("client_x"), + .client_y = parser.field("client_y"), + }; +} + +inline x::json::json ClientXY::to_json() const { + x::json::json j; + j["client_x"] = this->client_x; + j["client_y"] = this->client_y; + return j; +} + +inline Bounds Bounds::parse(x::json::Parser parser) { + return Bounds{ + .lower = parser.field("lower"), + .upper = parser.field("upper"), + }; +} + +inline x::json::json Bounds::to_json() const { + x::json::json j; + j["lower"] = this->lower; + j["upper"] = this->upper; + return j; +} + } diff --git a/x/cpp/spatial/proto.gen.h b/x/cpp/spatial/proto.gen.h index 4ed8412f87..4fb8710445 100644 --- a/x/cpp/spatial/proto.gen.h +++ b/x/cpp/spatial/proto.gen.h @@ -11,6 +11,8 @@ #pragma once +#include +#include #include #include "x/cpp/errors/errors.h" @@ -22,6 +24,78 @@ namespace x::spatial { +inline std::pair<::x::spatial::pb::XLocation, x::errors::Error> +x_location_to_pb(const std::string &cpp) { + static const std::unordered_map kMap = { + {X_LOCATION_LEFT, ::x::spatial::pb::X_LOCATION_LEFT}, + {X_LOCATION_RIGHT, ::x::spatial::pb::X_LOCATION_RIGHT}, + }; + auto it = kMap.find(cpp); + if (it == kMap.end()) + return {{}, x::errors::Error("unrecognized XLocation value: " + cpp)}; + return {it->second, x::errors::NIL}; +} + +inline std::pair +x_location_from_pb(::x::spatial::pb::XLocation pb) { + switch (pb) { + case ::x::spatial::pb::X_LOCATION_LEFT: + return {X_LOCATION_LEFT, x::errors::NIL}; + case ::x::spatial::pb::X_LOCATION_RIGHT: + return {X_LOCATION_RIGHT, x::errors::NIL}; + default: + return {"", x::errors::Error("unrecognized XLocation protobuf value")}; + } +} + +inline std::pair<::x::spatial::pb::YLocation, x::errors::Error> +y_location_to_pb(const std::string &cpp) { + static const std::unordered_map kMap = { + {Y_LOCATION_TOP, ::x::spatial::pb::Y_LOCATION_TOP}, + {Y_LOCATION_BOTTOM, ::x::spatial::pb::Y_LOCATION_BOTTOM}, + }; + auto it = kMap.find(cpp); + if (it == kMap.end()) + return {{}, x::errors::Error("unrecognized YLocation value: " + cpp)}; + return {it->second, x::errors::NIL}; +} + +inline std::pair +y_location_from_pb(::x::spatial::pb::YLocation pb) { + switch (pb) { + case ::x::spatial::pb::Y_LOCATION_TOP: + return {Y_LOCATION_TOP, x::errors::NIL}; + case ::x::spatial::pb::Y_LOCATION_BOTTOM: + return {Y_LOCATION_BOTTOM, x::errors::NIL}; + default: + return {"", x::errors::Error("unrecognized YLocation protobuf value")}; + } +} + +inline std::pair<::x::spatial::pb::StickyUnit, x::errors::Error> +sticky_unit_to_pb(const std::string &cpp) { + static const std::unordered_map kMap = { + {STICKY_UNIT_PX, ::x::spatial::pb::STICKY_UNIT_PX}, + {STICKY_UNIT_DECIMAL, ::x::spatial::pb::STICKY_UNIT_DECIMAL}, + }; + auto it = kMap.find(cpp); + if (it == kMap.end()) + return {{}, x::errors::Error("unrecognized StickyUnit value: " + cpp)}; + return {it->second, x::errors::NIL}; +} + +inline std::pair +sticky_unit_from_pb(::x::spatial::pb::StickyUnit pb) { + switch (pb) { + case ::x::spatial::pb::STICKY_UNIT_PX: + return {STICKY_UNIT_PX, x::errors::NIL}; + case ::x::spatial::pb::STICKY_UNIT_DECIMAL: + return {STICKY_UNIT_DECIMAL, x::errors::NIL}; + default: + return {"", x::errors::Error("unrecognized StickyUnit protobuf value")}; + } +} + inline std::pair<::x::spatial::pb::XY, x::errors::Error> XY::to_proto() const { ::x::spatial::pb::XY pb; pb.set_x(this->x); @@ -36,4 +110,191 @@ inline std::pair XY::from_proto(const ::x::spatial::pb::XY return {cpp, x::errors::NIL}; } +inline std::pair<::x::spatial::pb::CornerLocation, x::errors::Error> +CornerLocation::to_proto() const { + ::x::spatial::pb::CornerLocation pb; + { + auto [v, err] = x_location_to_pb(this->x); + if (err) return {{}, err}; + pb.set_x(v); + } + { + auto [v, err] = y_location_to_pb(this->y); + if (err) return {{}, err}; + pb.set_y(v); + } + return {pb, x::errors::NIL}; +} + +inline std::pair +CornerLocation::from_proto(const ::x::spatial::pb::CornerLocation &pb) { + CornerLocation cpp; + { + auto [v, err] = x_location_from_pb(pb.x()); + if (err) return {{}, err}; + cpp.x = v; + } + { + auto [v, err] = y_location_from_pb(pb.y()); + if (err) return {{}, err}; + cpp.y = v; + } + return {cpp, x::errors::NIL}; +} + +inline std::pair<::x::spatial::pb::StickyUnits, x::errors::Error> +StickyUnits::to_proto() const { + ::x::spatial::pb::StickyUnits pb; + { + auto [v, err] = sticky_unit_to_pb(this->x); + if (err) return {{}, err}; + pb.set_x(v); + } + { + auto [v, err] = sticky_unit_to_pb(this->y); + if (err) return {{}, err}; + pb.set_y(v); + } + return {pb, x::errors::NIL}; +} + +inline std::pair +StickyUnits::from_proto(const ::x::spatial::pb::StickyUnits &pb) { + StickyUnits cpp; + { + auto [v, err] = sticky_unit_from_pb(pb.x()); + if (err) return {{}, err}; + cpp.x = v; + } + { + auto [v, err] = sticky_unit_from_pb(pb.y()); + if (err) return {{}, err}; + cpp.y = v; + } + return {cpp, x::errors::NIL}; +} + +inline std::pair<::x::spatial::pb::StickyXY, x::errors::Error> +StickyXY::to_proto() const { + ::x::spatial::pb::StickyXY pb; + pb.set_x(this->x); + pb.set_y(this->y); + if (this->root.has_value()) { + auto [v, err] = this->root->to_proto(); + if (err) return {{}, err}; + *pb.mutable_root() = v; + } + if (this->units.has_value()) { + auto [v, err] = this->units->to_proto(); + if (err) return {{}, err}; + *pb.mutable_units() = v; + } + return {pb, x::errors::NIL}; +} + +inline std::pair +StickyXY::from_proto(const ::x::spatial::pb::StickyXY &pb) { + StickyXY cpp; + cpp.x = pb.x(); + cpp.y = pb.y(); + if (pb.has_root()) { + auto [v, err] = CornerLocation::from_proto(pb.root()); + if (err) return {{}, err}; + cpp.root = v; + } + if (pb.has_units()) { + auto [v, err] = StickyUnits::from_proto(pb.units()); + if (err) return {{}, err}; + cpp.units = v; + } + return {cpp, x::errors::NIL}; +} + +inline std::pair<::x::spatial::pb::Dimensions, x::errors::Error> +Dimensions::to_proto() const { + ::x::spatial::pb::Dimensions pb; + pb.set_width(this->width); + pb.set_height(this->height); + return {pb, x::errors::NIL}; +} + +inline std::pair +Dimensions::from_proto(const ::x::spatial::pb::Dimensions &pb) { + Dimensions cpp; + cpp.width = pb.width(); + cpp.height = pb.height(); + return {cpp, x::errors::NIL}; +} + +inline std::pair<::x::spatial::pb::Viewport, x::errors::Error> +Viewport::to_proto() const { + ::x::spatial::pb::Viewport pb; + pb.set_zoom(this->zoom); + { + auto [v, err] = this->position.to_proto(); + if (err) return {{}, err}; + *pb.mutable_position() = v; + } + return {pb, x::errors::NIL}; +} + +inline std::pair +Viewport::from_proto(const ::x::spatial::pb::Viewport &pb) { + Viewport cpp; + cpp.zoom = pb.zoom(); + { + auto [v, err] = XY::from_proto(pb.position()); + if (err) return {{}, err}; + cpp.position = v; + } + return {cpp, x::errors::NIL}; +} + +inline std::pair<::x::spatial::pb::SignedDimensions, x::errors::Error> +SignedDimensions::to_proto() const { + ::x::spatial::pb::SignedDimensions pb; + pb.set_signed_width(this->signed_width); + pb.set_signed_height(this->signed_height); + return {pb, x::errors::NIL}; +} + +inline std::pair +SignedDimensions::from_proto(const ::x::spatial::pb::SignedDimensions &pb) { + SignedDimensions cpp; + cpp.signed_width = pb.signed_width(); + cpp.signed_height = pb.signed_height(); + return {cpp, x::errors::NIL}; +} + +inline std::pair<::x::spatial::pb::ClientXY, x::errors::Error> +ClientXY::to_proto() const { + ::x::spatial::pb::ClientXY pb; + pb.set_client_x(this->client_x); + pb.set_client_y(this->client_y); + return {pb, x::errors::NIL}; +} + +inline std::pair +ClientXY::from_proto(const ::x::spatial::pb::ClientXY &pb) { + ClientXY cpp; + cpp.client_x = pb.client_x(); + cpp.client_y = pb.client_y(); + return {cpp, x::errors::NIL}; +} + +inline std::pair<::x::spatial::pb::Bounds, x::errors::Error> Bounds::to_proto() const { + ::x::spatial::pb::Bounds pb; + pb.set_lower(this->lower); + pb.set_upper(this->upper); + return {pb, x::errors::NIL}; +} + +inline std::pair +Bounds::from_proto(const ::x::spatial::pb::Bounds &pb) { + Bounds cpp; + cpp.lower = pb.lower(); + cpp.upper = pb.upper(); + return {cpp, x::errors::NIL}; +} + } diff --git a/x/cpp/spatial/types.gen.h b/x/cpp/spatial/types.gen.h index a810947970..ce0f57435b 100644 --- a/x/cpp/spatial/types.gen.h +++ b/x/cpp/spatial/types.gen.h @@ -11,6 +11,9 @@ #pragma once +#include +#include +#include #include #include "x/cpp/errors/errors.h" @@ -21,12 +24,55 @@ namespace x::spatial { struct XY; +struct Dimensions; +struct SignedDimensions; +struct ClientXY; +struct Viewport; +struct CornerLocation; +struct StickyUnits; +struct StickyXY; + +constexpr const char *X_LOCATION_LEFT = "left"; +constexpr const char *X_LOCATION_RIGHT = "right"; + +constexpr const char *Y_LOCATION_TOP = "top"; +constexpr const char *Y_LOCATION_BOTTOM = "bottom"; + +constexpr const char *STICKY_UNIT_PX = "px"; +constexpr const char *STICKY_UNIT_DECIMAL = "decimal"; constexpr const char *OUTER_LOCATION_TOP = "top"; constexpr const char *OUTER_LOCATION_RIGHT = "right"; constexpr const char *OUTER_LOCATION_BOTTOM = "bottom"; constexpr const char *OUTER_LOCATION_LEFT = "left"; +constexpr const char *DIRECTION_X = "x"; +constexpr const char *DIRECTION_Y = "y"; + +constexpr const char *ANGULAR_DIRECTION_CLOCKWISE = "clockwise"; +constexpr const char *ANGULAR_DIRECTION_COUNTERCLOCKWISE = "counterclockwise"; + +constexpr const char *CENTER_LOCATION_CENTER = "center"; + +constexpr const char *LOCATION_TOP = "top"; +constexpr const char *LOCATION_RIGHT = "right"; +constexpr const char *LOCATION_BOTTOM = "bottom"; +constexpr const char *LOCATION_LEFT = "left"; +constexpr const char *LOCATION_CENTER = "center"; + +constexpr const char *ALIGNMENT_START = "start"; +constexpr const char *ALIGNMENT_CENTER = "center"; +constexpr const char *ALIGNMENT_END = "end"; + +constexpr const char *ORDER_FIRST = "first"; +constexpr const char *ORDER_LAST = "last"; + +constexpr const char *DIMENSION_WIDTH = "width"; +constexpr const char *DIMENSION_HEIGHT = "height"; + +constexpr const char *SIGNED_DIMENSION_SIGNED_WIDTH = "signedWidth"; +constexpr const char *SIGNED_DIMENSION_SIGNED_HEIGHT = "signedHeight"; + /// @brief XY is a 2D coordinate point with x and y values. Used for positioning /// elements in two-dimensional space. struct XY { @@ -42,4 +88,149 @@ struct XY { [[nodiscard]] std::pair<::x::spatial::pb::XY, x::errors::Error> to_proto() const; static std::pair from_proto(const ::x::spatial::pb::XY &pb); }; + +/// @brief Dimensions is a 2D size with width and height values. +struct Dimensions { + /// @brief width is the width in pixels. + double width = 0; + /// @brief height is the height in pixels. + double height = 0; + + static Dimensions parse(x::json::Parser parser); + [[nodiscard]] x::json::json to_json() const; + + using proto_type = ::x::spatial::pb::Dimensions; + [[nodiscard]] std::pair<::x::spatial::pb::Dimensions, x::errors::Error> + to_proto() const; + static std::pair + from_proto(const ::x::spatial::pb::Dimensions &pb); +}; + +/// @brief SignedDimensions is a 2D size whose width and height components carry sign, +/// allowing negative values to express direction. +struct SignedDimensions { + /// @brief signed_width is the signed width. + double signed_width = 0; + /// @brief signed_height is the signed height. + double signed_height = 0; + + static SignedDimensions parse(x::json::Parser parser); + [[nodiscard]] x::json::json to_json() const; + + using proto_type = ::x::spatial::pb::SignedDimensions; + [[nodiscard]] std::pair<::x::spatial::pb::SignedDimensions, x::errors::Error> + to_proto() const; + static std::pair + from_proto(const ::x::spatial::pb::SignedDimensions &pb); +}; + +/// @brief ClientXY is a 2D coordinate point expressed in client (viewport) space, +/// matching the shape of DOM mouse events. +struct ClientXY { + /// @brief client_x is the horizontal coordinate in client (viewport) space. + double client_x = 0; + /// @brief client_y is the vertical coordinate in client (viewport) space. + double client_y = 0; + + static ClientXY parse(x::json::Parser parser); + [[nodiscard]] x::json::json to_json() const; + + using proto_type = ::x::spatial::pb::ClientXY; + [[nodiscard]] std::pair<::x::spatial::pb::ClientXY, x::errors::Error> + to_proto() const; + static std::pair + from_proto(const ::x::spatial::pb::ClientXY &pb); +}; + +/// @brief Bounds is a closed-open interval [lower, upper) over an ordered numeric value +/// space. The TypeScript binding is generic over T so callers can express bounds over +/// either number or bigint values; other languages emit a concrete float64-based type. +struct Bounds { + /// @brief lower is the inclusive lower bound. + double lower = 0; + /// @brief upper is the exclusive upper bound. + double upper = 0; + + static Bounds parse(x::json::Parser parser); + [[nodiscard]] x::json::json to_json() const; + + using proto_type = ::x::spatial::pb::Bounds; + [[nodiscard]] std::pair<::x::spatial::pb::Bounds, x::errors::Error> + to_proto() const; + static std::pair + from_proto(const ::x::spatial::pb::Bounds &pb); +}; + +/// @brief Viewport is the camera state of a viewport. +struct Viewport { + /// @brief zoom is the zoom level where 1.0 equals 100%. + double zoom = 0; + /// @brief position is the (x, y) pan offset of the viewport. + XY position; + + static Viewport parse(x::json::Parser parser); + [[nodiscard]] x::json::json to_json() const; + + using proto_type = ::x::spatial::pb::Viewport; + [[nodiscard]] std::pair<::x::spatial::pb::Viewport, x::errors::Error> + to_proto() const; + static std::pair + from_proto(const ::x::spatial::pb::Viewport &pb); +}; + +/// @brief CornerLocation is an anchor corner for positioning. +struct CornerLocation { + /// @brief x is the horizontal anchor. + std::string x; + /// @brief y is the vertical anchor. + std::string y; + + static CornerLocation parse(x::json::Parser parser); + [[nodiscard]] x::json::json to_json() const; + + using proto_type = ::x::spatial::pb::CornerLocation; + [[nodiscard]] std::pair<::x::spatial::pb::CornerLocation, x::errors::Error> + to_proto() const; + static std::pair + from_proto(const ::x::spatial::pb::CornerLocation &pb); +}; + +/// @brief StickyUnits specifies the measurement units for sticky positioning. +struct StickyUnits { + /// @brief x is the horizontal unit. + std::string x; + /// @brief y is the vertical unit. + std::string y; + + static StickyUnits parse(x::json::Parser parser); + [[nodiscard]] x::json::json to_json() const; + + using proto_type = ::x::spatial::pb::StickyUnits; + [[nodiscard]] std::pair<::x::spatial::pb::StickyUnits, x::errors::Error> + to_proto() const; + static std::pair + from_proto(const ::x::spatial::pb::StickyUnits &pb); +}; + +/// @brief StickyXY is a position that can be anchored to different corners of a +/// container with configurable units (pixels or decimal fractions). +struct StickyXY { + /// @brief x is the horizontal coordinate. + double x = 0; + /// @brief y is the vertical coordinate. + double y = 0; + /// @brief root is the optional anchor corner for the position. + std::optional root; + /// @brief units is the optional unit specification for the coordinates. + std::optional units; + + static StickyXY parse(x::json::Parser parser); + [[nodiscard]] x::json::json to_json() const; + + using proto_type = ::x::spatial::pb::StickyXY; + [[nodiscard]] std::pair<::x::spatial::pb::StickyXY, x::errors::Error> + to_proto() const; + static std::pair + from_proto(const ::x::spatial::pb::StickyXY &pb); +}; } diff --git a/x/go/pluralize/pluralize.go b/x/go/pluralize/pluralize.go index e3a5f79825..58597b5a6a 100644 --- a/x/go/pluralize/pluralize.go +++ b/x/go/pluralize/pluralize.go @@ -127,8 +127,11 @@ func String(name string) string { } if len(name) > 1 && lower[len(lower)-1] == 'y' { - // All-caps abbreviations (e.g. "XY", "ID") just get "s" - if isAllUpper(name) { + // Acronym ending in Y (e.g. "XY", "StickyXY"): the trailing "Y" is the + // letter Y of an uppercase abbreviation, not a consonant-y suffix. Add + // "s" rather than mangling to "ies". This subsumes the all-uppercase + // case ("XY" -> "XYs"). + if name[len(name)-1] == 'Y' && isUpper(name[len(name)-2]) { return name + "s" } if !isVowel(lower[len(lower)-2]) { diff --git a/x/go/pluralize/pluralize_test.go b/x/go/pluralize/pluralize_test.go index f47dcbbc2c..ded46f7acc 100644 --- a/x/go/pluralize/pluralize_test.go +++ b/x/go/pluralize/pluralize_test.go @@ -93,6 +93,11 @@ var _ = Describe("Pluralize", func() { Expect(pluralize.String("XY")).To(Equal("XYs")) Expect(pluralize.String("Array")).To(Equal("Arrays")) }) + It("Should add s when a trailing acronym ends in Y", func() { + Expect(pluralize.String("StickyXY")).To(Equal("StickyXYs")) + Expect(pluralize.String("ClientXY")).To(Equal("ClientXYs")) + Expect(pluralize.String("MyXY")).To(Equal("MyXYs")) + }) }) Context("already plural words", func() { diff --git a/x/go/spatial/pb/spatial.pb.go b/x/go/spatial/pb/spatial.pb.go index 7cc5ef4bee..7d94ef53cf 100644 --- a/x/go/spatial/pb/spatial.pb.go +++ b/x/go/spatial/pb/spatial.pb.go @@ -32,6 +32,148 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +// XLocation is a horizontal-axis location at the left or right edge. +type XLocation int32 + +const ( + XLocation_X_LOCATION_LEFT XLocation = 0 + XLocation_X_LOCATION_RIGHT XLocation = 1 +) + +// Enum value maps for XLocation. +var ( + XLocation_name = map[int32]string{ + 0: "X_LOCATION_LEFT", + 1: "X_LOCATION_RIGHT", + } + XLocation_value = map[string]int32{ + "X_LOCATION_LEFT": 0, + "X_LOCATION_RIGHT": 1, + } +) + +func (x XLocation) Enum() *XLocation { + p := new(XLocation) + *p = x + return p +} + +func (x XLocation) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (XLocation) Descriptor() protoreflect.EnumDescriptor { + return file_x_go_spatial_pb_spatial_proto_enumTypes[0].Descriptor() +} + +func (XLocation) Type() protoreflect.EnumType { + return &file_x_go_spatial_pb_spatial_proto_enumTypes[0] +} + +func (x XLocation) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use XLocation.Descriptor instead. +func (XLocation) EnumDescriptor() ([]byte, []int) { + return file_x_go_spatial_pb_spatial_proto_rawDescGZIP(), []int{0} +} + +// YLocation is a vertical-axis location at the top or bottom edge. +type YLocation int32 + +const ( + YLocation_Y_LOCATION_TOP YLocation = 0 + YLocation_Y_LOCATION_BOTTOM YLocation = 1 +) + +// Enum value maps for YLocation. +var ( + YLocation_name = map[int32]string{ + 0: "Y_LOCATION_TOP", + 1: "Y_LOCATION_BOTTOM", + } + YLocation_value = map[string]int32{ + "Y_LOCATION_TOP": 0, + "Y_LOCATION_BOTTOM": 1, + } +) + +func (x YLocation) Enum() *YLocation { + p := new(YLocation) + *p = x + return p +} + +func (x YLocation) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (YLocation) Descriptor() protoreflect.EnumDescriptor { + return file_x_go_spatial_pb_spatial_proto_enumTypes[1].Descriptor() +} + +func (YLocation) Type() protoreflect.EnumType { + return &file_x_go_spatial_pb_spatial_proto_enumTypes[1] +} + +func (x YLocation) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use YLocation.Descriptor instead. +func (YLocation) EnumDescriptor() ([]byte, []int) { + return file_x_go_spatial_pb_spatial_proto_rawDescGZIP(), []int{1} +} + +// StickyUnit is the measurement unit for a sticky coordinate, either pixels or a +// decimal fraction of the container. +type StickyUnit int32 + +const ( + StickyUnit_STICKY_UNIT_PX StickyUnit = 0 + StickyUnit_STICKY_UNIT_DECIMAL StickyUnit = 1 +) + +// Enum value maps for StickyUnit. +var ( + StickyUnit_name = map[int32]string{ + 0: "STICKY_UNIT_PX", + 1: "STICKY_UNIT_DECIMAL", + } + StickyUnit_value = map[string]int32{ + "STICKY_UNIT_PX": 0, + "STICKY_UNIT_DECIMAL": 1, + } +) + +func (x StickyUnit) Enum() *StickyUnit { + p := new(StickyUnit) + *p = x + return p +} + +func (x StickyUnit) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (StickyUnit) Descriptor() protoreflect.EnumDescriptor { + return file_x_go_spatial_pb_spatial_proto_enumTypes[2].Descriptor() +} + +func (StickyUnit) Type() protoreflect.EnumType { + return &file_x_go_spatial_pb_spatial_proto_enumTypes[2] +} + +func (x StickyUnit) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use StickyUnit.Descriptor instead. +func (StickyUnit) EnumDescriptor() ([]byte, []int) { + return file_x_go_spatial_pb_spatial_proto_rawDescGZIP(), []int{2} +} + // OuterLocation is a position indicator for elements anchored to the outer edge of a // container. Used for orientation and positioning of UI elements. type OuterLocation int32 @@ -70,11 +212,11 @@ func (x OuterLocation) String() string { } func (OuterLocation) Descriptor() protoreflect.EnumDescriptor { - return file_x_go_spatial_pb_spatial_proto_enumTypes[0].Descriptor() + return file_x_go_spatial_pb_spatial_proto_enumTypes[3].Descriptor() } func (OuterLocation) Type() protoreflect.EnumType { - return &file_x_go_spatial_pb_spatial_proto_enumTypes[0] + return &file_x_go_spatial_pb_spatial_proto_enumTypes[3] } func (x OuterLocation) Number() protoreflect.EnumNumber { @@ -83,78 +225,995 @@ func (x OuterLocation) Number() protoreflect.EnumNumber { // Deprecated: Use OuterLocation.Descriptor instead. func (OuterLocation) EnumDescriptor() ([]byte, []int) { - return file_x_go_spatial_pb_spatial_proto_rawDescGZIP(), []int{0} + return file_x_go_spatial_pb_spatial_proto_rawDescGZIP(), []int{3} } -// XY is a 2D coordinate point with x and y values. Used for positioning elements in -// two-dimensional space. -type XY struct { - state protoimpl.MessageState `protogen:"open.v1"` - // x is the horizontal coordinate. - X float64 `protobuf:"fixed64,1,opt,name=x,proto3" json:"x,omitempty"` - // y is the vertical coordinate. - Y float64 `protobuf:"fixed64,2,opt,name=y,proto3" json:"y,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache +// Direction is a 2D axis direction. +type Direction int32 + +const ( + Direction_DIRECTION_X Direction = 0 + Direction_DIRECTION_Y Direction = 1 +) + +// Enum value maps for Direction. +var ( + Direction_name = map[int32]string{ + 0: "DIRECTION_X", + 1: "DIRECTION_Y", + } + Direction_value = map[string]int32{ + "DIRECTION_X": 0, + "DIRECTION_Y": 1, + } +) + +func (x Direction) Enum() *Direction { + p := new(Direction) + *p = x + return p } -func (x *XY) Reset() { - *x = XY{} - mi := &file_x_go_spatial_pb_spatial_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) +func (x Direction) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } -func (x *XY) String() string { - return protoimpl.X.MessageStringOf(x) +func (Direction) Descriptor() protoreflect.EnumDescriptor { + return file_x_go_spatial_pb_spatial_proto_enumTypes[4].Descriptor() } -func (*XY) ProtoMessage() {} +func (Direction) Type() protoreflect.EnumType { + return &file_x_go_spatial_pb_spatial_proto_enumTypes[4] +} -func (x *XY) ProtoReflect() protoreflect.Message { - mi := &file_x_go_spatial_pb_spatial_proto_msgTypes[0] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms +func (x Direction) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Direction.Descriptor instead. +func (Direction) EnumDescriptor() ([]byte, []int) { + return file_x_go_spatial_pb_spatial_proto_rawDescGZIP(), []int{4} +} + +// AngularDirection is a rotational direction in 2D space. +type AngularDirection int32 + +const ( + AngularDirection_ANGULAR_DIRECTION_CLOCKWISE AngularDirection = 0 + AngularDirection_ANGULAR_DIRECTION_COUNTERCLOCKWISE AngularDirection = 1 +) + +// Enum value maps for AngularDirection. +var ( + AngularDirection_name = map[int32]string{ + 0: "ANGULAR_DIRECTION_CLOCKWISE", + 1: "ANGULAR_DIRECTION_COUNTERCLOCKWISE", } - return mi.MessageOf(x) + AngularDirection_value = map[string]int32{ + "ANGULAR_DIRECTION_CLOCKWISE": 0, + "ANGULAR_DIRECTION_COUNTERCLOCKWISE": 1, + } +) + +func (x AngularDirection) Enum() *AngularDirection { + p := new(AngularDirection) + *p = x + return p } -// Deprecated: Use XY.ProtoReflect.Descriptor instead. -func (*XY) Descriptor() ([]byte, []int) { - return file_x_go_spatial_pb_spatial_proto_rawDescGZIP(), []int{0} +func (x AngularDirection) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } -func (x *XY) GetX() float64 { - if x != nil { - return x.X +func (AngularDirection) Descriptor() protoreflect.EnumDescriptor { + return file_x_go_spatial_pb_spatial_proto_enumTypes[5].Descriptor() +} + +func (AngularDirection) Type() protoreflect.EnumType { + return &file_x_go_spatial_pb_spatial_proto_enumTypes[5] +} + +func (x AngularDirection) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use AngularDirection.Descriptor instead. +func (AngularDirection) EnumDescriptor() ([]byte, []int) { + return file_x_go_spatial_pb_spatial_proto_rawDescGZIP(), []int{5} +} + +// CenterLocation is a location at the center of a container. +type CenterLocation int32 + +const ( + CenterLocation_CENTER_LOCATION_CENTER CenterLocation = 0 +) + +// Enum value maps for CenterLocation. +var ( + CenterLocation_name = map[int32]string{ + 0: "CENTER_LOCATION_CENTER", } - return 0 + CenterLocation_value = map[string]int32{ + "CENTER_LOCATION_CENTER": 0, + } +) + +func (x CenterLocation) Enum() *CenterLocation { + p := new(CenterLocation) + *p = x + return p } -func (x *XY) GetY() float64 { - if x != nil { - return x.Y +func (x CenterLocation) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (CenterLocation) Descriptor() protoreflect.EnumDescriptor { + return file_x_go_spatial_pb_spatial_proto_enumTypes[6].Descriptor() +} + +func (CenterLocation) Type() protoreflect.EnumType { + return &file_x_go_spatial_pb_spatial_proto_enumTypes[6] +} + +func (x CenterLocation) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use CenterLocation.Descriptor instead. +func (CenterLocation) EnumDescriptor() ([]byte, []int) { + return file_x_go_spatial_pb_spatial_proto_rawDescGZIP(), []int{6} +} + +// Location is a position indicator covering the four outer edges of a container and its +// center. +type Location int32 + +const ( + Location_LOCATION_TOP Location = 0 + Location_LOCATION_RIGHT Location = 1 + Location_LOCATION_BOTTOM Location = 2 + Location_LOCATION_LEFT Location = 3 + Location_LOCATION_CENTER Location = 4 +) + +// Enum value maps for Location. +var ( + Location_name = map[int32]string{ + 0: "LOCATION_TOP", + 1: "LOCATION_RIGHT", + 2: "LOCATION_BOTTOM", + 3: "LOCATION_LEFT", + 4: "LOCATION_CENTER", } - return 0 + Location_value = map[string]int32{ + "LOCATION_TOP": 0, + "LOCATION_RIGHT": 1, + "LOCATION_BOTTOM": 2, + "LOCATION_LEFT": 3, + "LOCATION_CENTER": 4, + } +) + +func (x Location) Enum() *Location { + p := new(Location) + *p = x + return p } -var File_x_go_spatial_pb_spatial_proto protoreflect.FileDescriptor +func (x Location) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} -const file_x_go_spatial_pb_spatial_proto_rawDesc = "" + - "\n" + - "\x1dx/go/spatial/pb/spatial.proto\x12\fx.spatial.pb\" \n" + - "\x02XY\x12\f\n" + - "\x01x\x18\x01 \x01(\x01R\x01x\x12\f\n" + - "\x01y\x18\x02 \x01(\x01R\x01y*u\n" + - "\rOuterLocation\x12\x16\n" + - "\x12OUTER_LOCATION_TOP\x10\x00\x12\x18\n" + - "\x14OUTER_LOCATION_RIGHT\x10\x01\x12\x19\n" + - "\x15OUTER_LOCATION_BOTTOM\x10\x02\x12\x17\n" + - "\x13OUTER_LOCATION_LEFT\x10\x03B\x96\x01\n" + +func (Location) Descriptor() protoreflect.EnumDescriptor { + return file_x_go_spatial_pb_spatial_proto_enumTypes[7].Descriptor() +} + +func (Location) Type() protoreflect.EnumType { + return &file_x_go_spatial_pb_spatial_proto_enumTypes[7] +} + +func (x Location) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Location.Descriptor instead. +func (Location) EnumDescriptor() ([]byte, []int) { + return file_x_go_spatial_pb_spatial_proto_rawDescGZIP(), []int{7} +} + +// Alignment is a positioning indicator for aligning content along an axis within a +// container. +type Alignment int32 + +const ( + Alignment_ALIGNMENT_START Alignment = 0 + Alignment_ALIGNMENT_CENTER Alignment = 1 + Alignment_ALIGNMENT_END Alignment = 2 +) + +// Enum value maps for Alignment. +var ( + Alignment_name = map[int32]string{ + 0: "ALIGNMENT_START", + 1: "ALIGNMENT_CENTER", + 2: "ALIGNMENT_END", + } + Alignment_value = map[string]int32{ + "ALIGNMENT_START": 0, + "ALIGNMENT_CENTER": 1, + "ALIGNMENT_END": 2, + } +) + +func (x Alignment) Enum() *Alignment { + p := new(Alignment) + *p = x + return p +} + +func (x Alignment) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Alignment) Descriptor() protoreflect.EnumDescriptor { + return file_x_go_spatial_pb_spatial_proto_enumTypes[8].Descriptor() +} + +func (Alignment) Type() protoreflect.EnumType { + return &file_x_go_spatial_pb_spatial_proto_enumTypes[8] +} + +func (x Alignment) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Alignment.Descriptor instead. +func (Alignment) EnumDescriptor() ([]byte, []int) { + return file_x_go_spatial_pb_spatial_proto_rawDescGZIP(), []int{8} +} + +// Order is a positional ordering indicator for elements in a sequence. +type Order int32 + +const ( + Order_ORDER_FIRST Order = 0 + Order_ORDER_LAST Order = 1 +) + +// Enum value maps for Order. +var ( + Order_name = map[int32]string{ + 0: "ORDER_FIRST", + 1: "ORDER_LAST", + } + Order_value = map[string]int32{ + "ORDER_FIRST": 0, + "ORDER_LAST": 1, + } +) + +func (x Order) Enum() *Order { + p := new(Order) + *p = x + return p +} + +func (x Order) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Order) Descriptor() protoreflect.EnumDescriptor { + return file_x_go_spatial_pb_spatial_proto_enumTypes[9].Descriptor() +} + +func (Order) Type() protoreflect.EnumType { + return &file_x_go_spatial_pb_spatial_proto_enumTypes[9] +} + +func (x Order) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Order.Descriptor instead. +func (Order) EnumDescriptor() ([]byte, []int) { + return file_x_go_spatial_pb_spatial_proto_rawDescGZIP(), []int{9} +} + +// Dimension is the name of a 2D size axis. +type Dimension int32 + +const ( + Dimension_DIMENSION_WIDTH Dimension = 0 + Dimension_DIMENSION_HEIGHT Dimension = 1 +) + +// Enum value maps for Dimension. +var ( + Dimension_name = map[int32]string{ + 0: "DIMENSION_WIDTH", + 1: "DIMENSION_HEIGHT", + } + Dimension_value = map[string]int32{ + "DIMENSION_WIDTH": 0, + "DIMENSION_HEIGHT": 1, + } +) + +func (x Dimension) Enum() *Dimension { + p := new(Dimension) + *p = x + return p +} + +func (x Dimension) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Dimension) Descriptor() protoreflect.EnumDescriptor { + return file_x_go_spatial_pb_spatial_proto_enumTypes[10].Descriptor() +} + +func (Dimension) Type() protoreflect.EnumType { + return &file_x_go_spatial_pb_spatial_proto_enumTypes[10] +} + +func (x Dimension) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Dimension.Descriptor instead. +func (Dimension) EnumDescriptor() ([]byte, []int) { + return file_x_go_spatial_pb_spatial_proto_rawDescGZIP(), []int{10} +} + +// SignedDimension is the name of a 2D signed size axis. +type SignedDimension int32 + +const ( + SignedDimension_SIGNED_DIMENSION_SIGNED_WIDTH SignedDimension = 0 + SignedDimension_SIGNED_DIMENSION_SIGNED_HEIGHT SignedDimension = 1 +) + +// Enum value maps for SignedDimension. +var ( + SignedDimension_name = map[int32]string{ + 0: "SIGNED_DIMENSION_SIGNED_WIDTH", + 1: "SIGNED_DIMENSION_SIGNED_HEIGHT", + } + SignedDimension_value = map[string]int32{ + "SIGNED_DIMENSION_SIGNED_WIDTH": 0, + "SIGNED_DIMENSION_SIGNED_HEIGHT": 1, + } +) + +func (x SignedDimension) Enum() *SignedDimension { + p := new(SignedDimension) + *p = x + return p +} + +func (x SignedDimension) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (SignedDimension) Descriptor() protoreflect.EnumDescriptor { + return file_x_go_spatial_pb_spatial_proto_enumTypes[11].Descriptor() +} + +func (SignedDimension) Type() protoreflect.EnumType { + return &file_x_go_spatial_pb_spatial_proto_enumTypes[11] +} + +func (x SignedDimension) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use SignedDimension.Descriptor instead. +func (SignedDimension) EnumDescriptor() ([]byte, []int) { + return file_x_go_spatial_pb_spatial_proto_rawDescGZIP(), []int{11} +} + +// XY is a 2D coordinate point with x and y values. Used for positioning elements in +// two-dimensional space. +type XY struct { + state protoimpl.MessageState `protogen:"open.v1"` + // x is the horizontal coordinate. + X float64 `protobuf:"fixed64,1,opt,name=x,proto3" json:"x,omitempty"` + // y is the vertical coordinate. + Y float64 `protobuf:"fixed64,2,opt,name=y,proto3" json:"y,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *XY) Reset() { + *x = XY{} + mi := &file_x_go_spatial_pb_spatial_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *XY) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*XY) ProtoMessage() {} + +func (x *XY) ProtoReflect() protoreflect.Message { + mi := &file_x_go_spatial_pb_spatial_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use XY.ProtoReflect.Descriptor instead. +func (*XY) Descriptor() ([]byte, []int) { + return file_x_go_spatial_pb_spatial_proto_rawDescGZIP(), []int{0} +} + +func (x *XY) GetX() float64 { + if x != nil { + return x.X + } + return 0 +} + +func (x *XY) GetY() float64 { + if x != nil { + return x.Y + } + return 0 +} + +// CornerLocation is an anchor corner for positioning. +type CornerLocation struct { + state protoimpl.MessageState `protogen:"open.v1"` + // x is the horizontal anchor. + X XLocation `protobuf:"varint,1,opt,name=x,proto3,enum=x.spatial.pb.XLocation" json:"x,omitempty"` + // y is the vertical anchor. + Y YLocation `protobuf:"varint,2,opt,name=y,proto3,enum=x.spatial.pb.YLocation" json:"y,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CornerLocation) Reset() { + *x = CornerLocation{} + mi := &file_x_go_spatial_pb_spatial_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CornerLocation) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CornerLocation) ProtoMessage() {} + +func (x *CornerLocation) ProtoReflect() protoreflect.Message { + mi := &file_x_go_spatial_pb_spatial_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CornerLocation.ProtoReflect.Descriptor instead. +func (*CornerLocation) Descriptor() ([]byte, []int) { + return file_x_go_spatial_pb_spatial_proto_rawDescGZIP(), []int{1} +} + +func (x *CornerLocation) GetX() XLocation { + if x != nil { + return x.X + } + return XLocation_X_LOCATION_LEFT +} + +func (x *CornerLocation) GetY() YLocation { + if x != nil { + return x.Y + } + return YLocation_Y_LOCATION_TOP +} + +// StickyUnits specifies the measurement units for sticky positioning. +type StickyUnits struct { + state protoimpl.MessageState `protogen:"open.v1"` + // x is the horizontal unit. + X StickyUnit `protobuf:"varint,1,opt,name=x,proto3,enum=x.spatial.pb.StickyUnit" json:"x,omitempty"` + // y is the vertical unit. + Y StickyUnit `protobuf:"varint,2,opt,name=y,proto3,enum=x.spatial.pb.StickyUnit" json:"y,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *StickyUnits) Reset() { + *x = StickyUnits{} + mi := &file_x_go_spatial_pb_spatial_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StickyUnits) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StickyUnits) ProtoMessage() {} + +func (x *StickyUnits) ProtoReflect() protoreflect.Message { + mi := &file_x_go_spatial_pb_spatial_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StickyUnits.ProtoReflect.Descriptor instead. +func (*StickyUnits) Descriptor() ([]byte, []int) { + return file_x_go_spatial_pb_spatial_proto_rawDescGZIP(), []int{2} +} + +func (x *StickyUnits) GetX() StickyUnit { + if x != nil { + return x.X + } + return StickyUnit_STICKY_UNIT_PX +} + +func (x *StickyUnits) GetY() StickyUnit { + if x != nil { + return x.Y + } + return StickyUnit_STICKY_UNIT_PX +} + +// StickyXY is a position that can be anchored to different corners of a container with +// configurable units (pixels or decimal fractions). +type StickyXY struct { + state protoimpl.MessageState `protogen:"open.v1"` + // x is the horizontal coordinate. + X float64 `protobuf:"fixed64,1,opt,name=x,proto3" json:"x,omitempty"` + // y is the vertical coordinate. + Y float64 `protobuf:"fixed64,2,opt,name=y,proto3" json:"y,omitempty"` + // root is the optional anchor corner for the position. + Root *CornerLocation `protobuf:"bytes,3,opt,name=root,proto3,oneof" json:"root,omitempty"` + // units is the optional unit specification for the coordinates. + Units *StickyUnits `protobuf:"bytes,4,opt,name=units,proto3,oneof" json:"units,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *StickyXY) Reset() { + *x = StickyXY{} + mi := &file_x_go_spatial_pb_spatial_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StickyXY) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StickyXY) ProtoMessage() {} + +func (x *StickyXY) ProtoReflect() protoreflect.Message { + mi := &file_x_go_spatial_pb_spatial_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StickyXY.ProtoReflect.Descriptor instead. +func (*StickyXY) Descriptor() ([]byte, []int) { + return file_x_go_spatial_pb_spatial_proto_rawDescGZIP(), []int{3} +} + +func (x *StickyXY) GetX() float64 { + if x != nil { + return x.X + } + return 0 +} + +func (x *StickyXY) GetY() float64 { + if x != nil { + return x.Y + } + return 0 +} + +func (x *StickyXY) GetRoot() *CornerLocation { + if x != nil { + return x.Root + } + return nil +} + +func (x *StickyXY) GetUnits() *StickyUnits { + if x != nil { + return x.Units + } + return nil +} + +// Dimensions is a 2D size with width and height values. +type Dimensions struct { + state protoimpl.MessageState `protogen:"open.v1"` + // width is the width in pixels. + Width float64 `protobuf:"fixed64,1,opt,name=width,proto3" json:"width,omitempty"` + // height is the height in pixels. + Height float64 `protobuf:"fixed64,2,opt,name=height,proto3" json:"height,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Dimensions) Reset() { + *x = Dimensions{} + mi := &file_x_go_spatial_pb_spatial_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Dimensions) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Dimensions) ProtoMessage() {} + +func (x *Dimensions) ProtoReflect() protoreflect.Message { + mi := &file_x_go_spatial_pb_spatial_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Dimensions.ProtoReflect.Descriptor instead. +func (*Dimensions) Descriptor() ([]byte, []int) { + return file_x_go_spatial_pb_spatial_proto_rawDescGZIP(), []int{4} +} + +func (x *Dimensions) GetWidth() float64 { + if x != nil { + return x.Width + } + return 0 +} + +func (x *Dimensions) GetHeight() float64 { + if x != nil { + return x.Height + } + return 0 +} + +// Viewport is the camera state of a viewport. +type Viewport struct { + state protoimpl.MessageState `protogen:"open.v1"` + // zoom is the zoom level where 1.0 equals 100%. + Zoom float64 `protobuf:"fixed64,1,opt,name=zoom,proto3" json:"zoom,omitempty"` + // position is the (x, y) pan offset of the viewport. + Position *XY `protobuf:"bytes,2,opt,name=position,proto3" json:"position,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Viewport) Reset() { + *x = Viewport{} + mi := &file_x_go_spatial_pb_spatial_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Viewport) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Viewport) ProtoMessage() {} + +func (x *Viewport) ProtoReflect() protoreflect.Message { + mi := &file_x_go_spatial_pb_spatial_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Viewport.ProtoReflect.Descriptor instead. +func (*Viewport) Descriptor() ([]byte, []int) { + return file_x_go_spatial_pb_spatial_proto_rawDescGZIP(), []int{5} +} + +func (x *Viewport) GetZoom() float64 { + if x != nil { + return x.Zoom + } + return 0 +} + +func (x *Viewport) GetPosition() *XY { + if x != nil { + return x.Position + } + return nil +} + +// SignedDimensions is a 2D size whose width and height components carry sign, allowing +// negative values to express direction. +type SignedDimensions struct { + state protoimpl.MessageState `protogen:"open.v1"` + // signed_width is the signed width. + SignedWidth float64 `protobuf:"fixed64,1,opt,name=signed_width,json=signedWidth,proto3" json:"signed_width,omitempty"` + // signed_height is the signed height. + SignedHeight float64 `protobuf:"fixed64,2,opt,name=signed_height,json=signedHeight,proto3" json:"signed_height,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SignedDimensions) Reset() { + *x = SignedDimensions{} + mi := &file_x_go_spatial_pb_spatial_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SignedDimensions) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SignedDimensions) ProtoMessage() {} + +func (x *SignedDimensions) ProtoReflect() protoreflect.Message { + mi := &file_x_go_spatial_pb_spatial_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SignedDimensions.ProtoReflect.Descriptor instead. +func (*SignedDimensions) Descriptor() ([]byte, []int) { + return file_x_go_spatial_pb_spatial_proto_rawDescGZIP(), []int{6} +} + +func (x *SignedDimensions) GetSignedWidth() float64 { + if x != nil { + return x.SignedWidth + } + return 0 +} + +func (x *SignedDimensions) GetSignedHeight() float64 { + if x != nil { + return x.SignedHeight + } + return 0 +} + +// ClientXY is a 2D coordinate point expressed in client (viewport) space, matching the +// shape of DOM mouse events. +type ClientXY struct { + state protoimpl.MessageState `protogen:"open.v1"` + // client_x is the horizontal coordinate in client (viewport) space. + ClientX float64 `protobuf:"fixed64,1,opt,name=client_x,json=clientX,proto3" json:"client_x,omitempty"` + // client_y is the vertical coordinate in client (viewport) space. + ClientY float64 `protobuf:"fixed64,2,opt,name=client_y,json=clientY,proto3" json:"client_y,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ClientXY) Reset() { + *x = ClientXY{} + mi := &file_x_go_spatial_pb_spatial_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClientXY) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClientXY) ProtoMessage() {} + +func (x *ClientXY) ProtoReflect() protoreflect.Message { + mi := &file_x_go_spatial_pb_spatial_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClientXY.ProtoReflect.Descriptor instead. +func (*ClientXY) Descriptor() ([]byte, []int) { + return file_x_go_spatial_pb_spatial_proto_rawDescGZIP(), []int{7} +} + +func (x *ClientXY) GetClientX() float64 { + if x != nil { + return x.ClientX + } + return 0 +} + +func (x *ClientXY) GetClientY() float64 { + if x != nil { + return x.ClientY + } + return 0 +} + +// Bounds is a closed-open interval [lower, upper) over an ordered numeric value space. +// The TypeScript binding is generic over T so callers can express bounds over either +// number or bigint values; other languages emit a concrete float64-based type. +type Bounds struct { + state protoimpl.MessageState `protogen:"open.v1"` + // lower is the inclusive lower bound. + Lower float64 `protobuf:"fixed64,1,opt,name=lower,proto3" json:"lower,omitempty"` + // upper is the exclusive upper bound. + Upper float64 `protobuf:"fixed64,2,opt,name=upper,proto3" json:"upper,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Bounds) Reset() { + *x = Bounds{} + mi := &file_x_go_spatial_pb_spatial_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Bounds) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Bounds) ProtoMessage() {} + +func (x *Bounds) ProtoReflect() protoreflect.Message { + mi := &file_x_go_spatial_pb_spatial_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Bounds.ProtoReflect.Descriptor instead. +func (*Bounds) Descriptor() ([]byte, []int) { + return file_x_go_spatial_pb_spatial_proto_rawDescGZIP(), []int{8} +} + +func (x *Bounds) GetLower() float64 { + if x != nil { + return x.Lower + } + return 0 +} + +func (x *Bounds) GetUpper() float64 { + if x != nil { + return x.Upper + } + return 0 +} + +var File_x_go_spatial_pb_spatial_proto protoreflect.FileDescriptor + +const file_x_go_spatial_pb_spatial_proto_rawDesc = "" + + "\n" + + "\x1dx/go/spatial/pb/spatial.proto\x12\fx.spatial.pb\" \n" + + "\x02XY\x12\f\n" + + "\x01x\x18\x01 \x01(\x01R\x01x\x12\f\n" + + "\x01y\x18\x02 \x01(\x01R\x01y\"^\n" + + "\x0eCornerLocation\x12%\n" + + "\x01x\x18\x01 \x01(\x0e2\x17.x.spatial.pb.XLocationR\x01x\x12%\n" + + "\x01y\x18\x02 \x01(\x0e2\x17.x.spatial.pb.YLocationR\x01y\"]\n" + + "\vStickyUnits\x12&\n" + + "\x01x\x18\x01 \x01(\x0e2\x18.x.spatial.pb.StickyUnitR\x01x\x12&\n" + + "\x01y\x18\x02 \x01(\x0e2\x18.x.spatial.pb.StickyUnitR\x01y\"\xa6\x01\n" + + "\bStickyXY\x12\f\n" + + "\x01x\x18\x01 \x01(\x01R\x01x\x12\f\n" + + "\x01y\x18\x02 \x01(\x01R\x01y\x125\n" + + "\x04root\x18\x03 \x01(\v2\x1c.x.spatial.pb.CornerLocationH\x00R\x04root\x88\x01\x01\x124\n" + + "\x05units\x18\x04 \x01(\v2\x19.x.spatial.pb.StickyUnitsH\x01R\x05units\x88\x01\x01B\a\n" + + "\x05_rootB\b\n" + + "\x06_units\":\n" + + "\n" + + "Dimensions\x12\x14\n" + + "\x05width\x18\x01 \x01(\x01R\x05width\x12\x16\n" + + "\x06height\x18\x02 \x01(\x01R\x06height\"L\n" + + "\bViewport\x12\x12\n" + + "\x04zoom\x18\x01 \x01(\x01R\x04zoom\x12,\n" + + "\bposition\x18\x02 \x01(\v2\x10.x.spatial.pb.XYR\bposition\"Z\n" + + "\x10SignedDimensions\x12!\n" + + "\fsigned_width\x18\x01 \x01(\x01R\vsignedWidth\x12#\n" + + "\rsigned_height\x18\x02 \x01(\x01R\fsignedHeight\"@\n" + + "\bClientXY\x12\x19\n" + + "\bclient_x\x18\x01 \x01(\x01R\aclientX\x12\x19\n" + + "\bclient_y\x18\x02 \x01(\x01R\aclientY\"4\n" + + "\x06Bounds\x12\x14\n" + + "\x05lower\x18\x01 \x01(\x01R\x05lower\x12\x14\n" + + "\x05upper\x18\x02 \x01(\x01R\x05upper*6\n" + + "\tXLocation\x12\x13\n" + + "\x0fX_LOCATION_LEFT\x10\x00\x12\x14\n" + + "\x10X_LOCATION_RIGHT\x10\x01*6\n" + + "\tYLocation\x12\x12\n" + + "\x0eY_LOCATION_TOP\x10\x00\x12\x15\n" + + "\x11Y_LOCATION_BOTTOM\x10\x01*9\n" + + "\n" + + "StickyUnit\x12\x12\n" + + "\x0eSTICKY_UNIT_PX\x10\x00\x12\x17\n" + + "\x13STICKY_UNIT_DECIMAL\x10\x01*u\n" + + "\rOuterLocation\x12\x16\n" + + "\x12OUTER_LOCATION_TOP\x10\x00\x12\x18\n" + + "\x14OUTER_LOCATION_RIGHT\x10\x01\x12\x19\n" + + "\x15OUTER_LOCATION_BOTTOM\x10\x02\x12\x17\n" + + "\x13OUTER_LOCATION_LEFT\x10\x03*-\n" + + "\tDirection\x12\x0f\n" + + "\vDIRECTION_X\x10\x00\x12\x0f\n" + + "\vDIRECTION_Y\x10\x01*[\n" + + "\x10AngularDirection\x12\x1f\n" + + "\x1bANGULAR_DIRECTION_CLOCKWISE\x10\x00\x12&\n" + + "\"ANGULAR_DIRECTION_COUNTERCLOCKWISE\x10\x01*,\n" + + "\x0eCenterLocation\x12\x1a\n" + + "\x16CENTER_LOCATION_CENTER\x10\x00*m\n" + + "\bLocation\x12\x10\n" + + "\fLOCATION_TOP\x10\x00\x12\x12\n" + + "\x0eLOCATION_RIGHT\x10\x01\x12\x13\n" + + "\x0fLOCATION_BOTTOM\x10\x02\x12\x11\n" + + "\rLOCATION_LEFT\x10\x03\x12\x13\n" + + "\x0fLOCATION_CENTER\x10\x04*I\n" + + "\tAlignment\x12\x13\n" + + "\x0fALIGNMENT_START\x10\x00\x12\x14\n" + + "\x10ALIGNMENT_CENTER\x10\x01\x12\x11\n" + + "\rALIGNMENT_END\x10\x02*(\n" + + "\x05Order\x12\x0f\n" + + "\vORDER_FIRST\x10\x00\x12\x0e\n" + + "\n" + + "ORDER_LAST\x10\x01*6\n" + + "\tDimension\x12\x13\n" + + "\x0fDIMENSION_WIDTH\x10\x00\x12\x14\n" + + "\x10DIMENSION_HEIGHT\x10\x01*X\n" + + "\x0fSignedDimension\x12!\n" + + "\x1dSIGNED_DIMENSION_SIGNED_WIDTH\x10\x00\x12\"\n" + + "\x1eSIGNED_DIMENSION_SIGNED_HEIGHT\x10\x01B\x96\x01\n" + "\x10com.x.spatial.pbB\fSpatialProtoP\x01Z\"github.com/synnaxlabs/x/spatial/pb\xa2\x02\x03XSP\xaa\x02\fX.Spatial.Pb\xca\x02\fX\\Spatial\\Pb\xe2\x02\x18X\\Spatial\\Pb\\GPBMetadata\xea\x02\x0eX::Spatial::Pbb\x06proto3" var ( @@ -169,18 +1228,44 @@ func file_x_go_spatial_pb_spatial_proto_rawDescGZIP() []byte { return file_x_go_spatial_pb_spatial_proto_rawDescData } -var file_x_go_spatial_pb_spatial_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_x_go_spatial_pb_spatial_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_x_go_spatial_pb_spatial_proto_enumTypes = make([]protoimpl.EnumInfo, 12) +var file_x_go_spatial_pb_spatial_proto_msgTypes = make([]protoimpl.MessageInfo, 9) var file_x_go_spatial_pb_spatial_proto_goTypes = []any{ - (OuterLocation)(0), // 0: x.spatial.pb.OuterLocation - (*XY)(nil), // 1: x.spatial.pb.XY + (XLocation)(0), // 0: x.spatial.pb.XLocation + (YLocation)(0), // 1: x.spatial.pb.YLocation + (StickyUnit)(0), // 2: x.spatial.pb.StickyUnit + (OuterLocation)(0), // 3: x.spatial.pb.OuterLocation + (Direction)(0), // 4: x.spatial.pb.Direction + (AngularDirection)(0), // 5: x.spatial.pb.AngularDirection + (CenterLocation)(0), // 6: x.spatial.pb.CenterLocation + (Location)(0), // 7: x.spatial.pb.Location + (Alignment)(0), // 8: x.spatial.pb.Alignment + (Order)(0), // 9: x.spatial.pb.Order + (Dimension)(0), // 10: x.spatial.pb.Dimension + (SignedDimension)(0), // 11: x.spatial.pb.SignedDimension + (*XY)(nil), // 12: x.spatial.pb.XY + (*CornerLocation)(nil), // 13: x.spatial.pb.CornerLocation + (*StickyUnits)(nil), // 14: x.spatial.pb.StickyUnits + (*StickyXY)(nil), // 15: x.spatial.pb.StickyXY + (*Dimensions)(nil), // 16: x.spatial.pb.Dimensions + (*Viewport)(nil), // 17: x.spatial.pb.Viewport + (*SignedDimensions)(nil), // 18: x.spatial.pb.SignedDimensions + (*ClientXY)(nil), // 19: x.spatial.pb.ClientXY + (*Bounds)(nil), // 20: x.spatial.pb.Bounds } var file_x_go_spatial_pb_spatial_proto_depIdxs = []int32{ - 0, // [0:0] is the sub-list for method output_type - 0, // [0:0] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name + 0, // 0: x.spatial.pb.CornerLocation.x:type_name -> x.spatial.pb.XLocation + 1, // 1: x.spatial.pb.CornerLocation.y:type_name -> x.spatial.pb.YLocation + 2, // 2: x.spatial.pb.StickyUnits.x:type_name -> x.spatial.pb.StickyUnit + 2, // 3: x.spatial.pb.StickyUnits.y:type_name -> x.spatial.pb.StickyUnit + 13, // 4: x.spatial.pb.StickyXY.root:type_name -> x.spatial.pb.CornerLocation + 14, // 5: x.spatial.pb.StickyXY.units:type_name -> x.spatial.pb.StickyUnits + 12, // 6: x.spatial.pb.Viewport.position:type_name -> x.spatial.pb.XY + 7, // [7:7] is the sub-list for method output_type + 7, // [7:7] is the sub-list for method input_type + 7, // [7:7] is the sub-list for extension type_name + 7, // [7:7] is the sub-list for extension extendee + 0, // [0:7] is the sub-list for field type_name } func init() { file_x_go_spatial_pb_spatial_proto_init() } @@ -188,13 +1273,14 @@ func file_x_go_spatial_pb_spatial_proto_init() { if File_x_go_spatial_pb_spatial_proto != nil { return } + file_x_go_spatial_pb_spatial_proto_msgTypes[3].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_x_go_spatial_pb_spatial_proto_rawDesc), len(file_x_go_spatial_pb_spatial_proto_rawDesc)), - NumEnums: 1, - NumMessages: 1, + NumEnums: 12, + NumMessages: 9, NumExtensions: 0, NumServices: 0, }, diff --git a/x/go/spatial/pb/spatial.proto b/x/go/spatial/pb/spatial.proto index 76bad31ba1..6a7a9bba4e 100644 --- a/x/go/spatial/pb/spatial.proto +++ b/x/go/spatial/pb/spatial.proto @@ -15,6 +15,25 @@ package x.spatial.pb; option go_package = "github.com/synnaxlabs/x/spatial/pb"; +// XLocation is a horizontal-axis location at the left or right edge. +enum XLocation { + X_LOCATION_LEFT = 0; + X_LOCATION_RIGHT = 1; +} + +// YLocation is a vertical-axis location at the top or bottom edge. +enum YLocation { + Y_LOCATION_TOP = 0; + Y_LOCATION_BOTTOM = 1; +} + +// StickyUnit is the measurement unit for a sticky coordinate, either pixels or a +// decimal fraction of the container. +enum StickyUnit { + STICKY_UNIT_PX = 0; + STICKY_UNIT_DECIMAL = 1; +} + // OuterLocation is a position indicator for elements anchored to the outer edge of a // container. Used for orientation and positioning of UI elements. enum OuterLocation { @@ -24,6 +43,59 @@ enum OuterLocation { OUTER_LOCATION_LEFT = 3; } +// Direction is a 2D axis direction. +enum Direction { + DIRECTION_X = 0; + DIRECTION_Y = 1; +} + +// AngularDirection is a rotational direction in 2D space. +enum AngularDirection { + ANGULAR_DIRECTION_CLOCKWISE = 0; + ANGULAR_DIRECTION_COUNTERCLOCKWISE = 1; +} + +// CenterLocation is a location at the center of a container. +enum CenterLocation { + CENTER_LOCATION_CENTER = 0; +} + +// Location is a position indicator covering the four outer edges of a container and its +// center. +enum Location { + LOCATION_TOP = 0; + LOCATION_RIGHT = 1; + LOCATION_BOTTOM = 2; + LOCATION_LEFT = 3; + LOCATION_CENTER = 4; +} + +// Alignment is a positioning indicator for aligning content along an axis within a +// container. +enum Alignment { + ALIGNMENT_START = 0; + ALIGNMENT_CENTER = 1; + ALIGNMENT_END = 2; +} + +// Order is a positional ordering indicator for elements in a sequence. +enum Order { + ORDER_FIRST = 0; + ORDER_LAST = 1; +} + +// Dimension is the name of a 2D size axis. +enum Dimension { + DIMENSION_WIDTH = 0; + DIMENSION_HEIGHT = 1; +} + +// SignedDimension is the name of a 2D signed size axis. +enum SignedDimension { + SIGNED_DIMENSION_SIGNED_WIDTH = 0; + SIGNED_DIMENSION_SIGNED_HEIGHT = 1; +} + // XY is a 2D coordinate point with x and y values. Used for positioning elements in // two-dimensional space. message XY { @@ -32,3 +104,76 @@ message XY { // y is the vertical coordinate. double y = 2; } + +// CornerLocation is an anchor corner for positioning. +message CornerLocation { + // x is the horizontal anchor. + XLocation x = 1; + // y is the vertical anchor. + YLocation y = 2; +} + +// StickyUnits specifies the measurement units for sticky positioning. +message StickyUnits { + // x is the horizontal unit. + StickyUnit x = 1; + // y is the vertical unit. + StickyUnit y = 2; +} + +// StickyXY is a position that can be anchored to different corners of a container with +// configurable units (pixels or decimal fractions). +message StickyXY { + // x is the horizontal coordinate. + double x = 1; + // y is the vertical coordinate. + double y = 2; + // root is the optional anchor corner for the position. + optional CornerLocation root = 3; + // units is the optional unit specification for the coordinates. + optional StickyUnits units = 4; +} + +// Dimensions is a 2D size with width and height values. +message Dimensions { + // width is the width in pixels. + double width = 1; + // height is the height in pixels. + double height = 2; +} + +// Viewport is the camera state of a viewport. +message Viewport { + // zoom is the zoom level where 1.0 equals 100%. + double zoom = 1; + // position is the (x, y) pan offset of the viewport. + XY position = 2; +} + +// SignedDimensions is a 2D size whose width and height components carry sign, allowing +// negative values to express direction. +message SignedDimensions { + // signed_width is the signed width. + double signed_width = 1; + // signed_height is the signed height. + double signed_height = 2; +} + +// ClientXY is a 2D coordinate point expressed in client (viewport) space, matching the +// shape of DOM mouse events. +message ClientXY { + // client_x is the horizontal coordinate in client (viewport) space. + double client_x = 1; + // client_y is the vertical coordinate in client (viewport) space. + double client_y = 2; +} + +// Bounds is a closed-open interval [lower, upper) over an ordered numeric value space. +// The TypeScript binding is generic over T so callers can express bounds over either +// number or bigint values; other languages emit a concrete float64-based type. +message Bounds { + // lower is the inclusive lower bound. + double lower = 1; + // upper is the exclusive upper bound. + double upper = 2; +} diff --git a/x/go/spatial/pb/translator.gen.go b/x/go/spatial/pb/translator.gen.go index c981bb4b13..8385498646 100644 --- a/x/go/spatial/pb/translator.gen.go +++ b/x/go/spatial/pb/translator.gen.go @@ -62,6 +62,574 @@ func XYsFromPB(pbs []*XY) ([]spatial.XY, error) { return result, nil } +// CornerLocationToPB converts CornerLocation to CornerLocation. +func CornerLocationToPB(r spatial.CornerLocation) (*CornerLocation, error) { + xVal, err := XLocationToPB(r.X) + if err != nil { + return nil, err + } + yVal, err := YLocationToPB(r.Y) + if err != nil { + return nil, err + } + pb := &CornerLocation{ + X: xVal, + Y: yVal, + } + return pb, nil +} + +// CornerLocationFromPB converts CornerLocation to CornerLocation. +func CornerLocationFromPB(pb *CornerLocation) (spatial.CornerLocation, error) { + var r spatial.CornerLocation + if pb == nil { + return r, nil + } + var err error + r.X, err = XLocationFromPB(pb.X) + if err != nil { + return spatial.CornerLocation{}, err + } + r.Y, err = YLocationFromPB(pb.Y) + if err != nil { + return spatial.CornerLocation{}, err + } + return r, nil +} + +// CornerLocationsToPB converts a slice of CornerLocation to CornerLocation. +func CornerLocationsToPB(rs []spatial.CornerLocation) ([]*CornerLocation, error) { + result := make([]*CornerLocation, len(rs)) + for i := range rs { + var err error + result[i], err = CornerLocationToPB(rs[i]) + if err != nil { + return nil, err + } + } + return result, nil +} + +// CornerLocationsFromPB converts a slice of CornerLocation to CornerLocation. +func CornerLocationsFromPB(pbs []*CornerLocation) ([]spatial.CornerLocation, error) { + result := make([]spatial.CornerLocation, len(pbs)) + for i, pb := range pbs { + var err error + result[i], err = CornerLocationFromPB(pb) + if err != nil { + return nil, err + } + } + return result, nil +} + +// StickyUnitsToPB converts StickyUnits to StickyUnits. +func StickyUnitsToPB(r spatial.StickyUnits) (*StickyUnits, error) { + xVal, err := StickyUnitToPB(r.X) + if err != nil { + return nil, err + } + yVal, err := StickyUnitToPB(r.Y) + if err != nil { + return nil, err + } + pb := &StickyUnits{ + X: xVal, + Y: yVal, + } + return pb, nil +} + +// StickyUnitsFromPB converts StickyUnits to StickyUnits. +func StickyUnitsFromPB(pb *StickyUnits) (spatial.StickyUnits, error) { + var r spatial.StickyUnits + if pb == nil { + return r, nil + } + var err error + r.X, err = StickyUnitFromPB(pb.X) + if err != nil { + return spatial.StickyUnits{}, err + } + r.Y, err = StickyUnitFromPB(pb.Y) + if err != nil { + return spatial.StickyUnits{}, err + } + return r, nil +} + +// StickyUnitsListToPB converts a slice of StickyUnits to StickyUnits. +func StickyUnitsListToPB(rs []spatial.StickyUnits) ([]*StickyUnits, error) { + result := make([]*StickyUnits, len(rs)) + for i := range rs { + var err error + result[i], err = StickyUnitsToPB(rs[i]) + if err != nil { + return nil, err + } + } + return result, nil +} + +// StickyUnitsListFromPB converts a slice of StickyUnits to StickyUnits. +func StickyUnitsListFromPB(pbs []*StickyUnits) ([]spatial.StickyUnits, error) { + result := make([]spatial.StickyUnits, len(pbs)) + for i, pb := range pbs { + var err error + result[i], err = StickyUnitsFromPB(pb) + if err != nil { + return nil, err + } + } + return result, nil +} + +// StickyXYToPB converts StickyXY to StickyXY. +func StickyXYToPB(r spatial.StickyXY) (*StickyXY, error) { + pb := &StickyXY{ + X: r.X, + Y: r.Y, + } + if r.Root != nil { + var err error + pb.Root, err = CornerLocationToPB(*r.Root) + if err != nil { + return nil, err + } + } + if r.Units != nil { + var err error + pb.Units, err = StickyUnitsToPB(*r.Units) + if err != nil { + return nil, err + } + } + return pb, nil +} + +// StickyXYFromPB converts StickyXY to StickyXY. +func StickyXYFromPB(pb *StickyXY) (spatial.StickyXY, error) { + var r spatial.StickyXY + if pb == nil { + return r, nil + } + r.X = pb.X + r.Y = pb.Y + if pb.Root != nil { + val, err := CornerLocationFromPB(pb.Root) + if err != nil { + return spatial.StickyXY{}, err + } + r.Root = &val + } + if pb.Units != nil { + val, err := StickyUnitsFromPB(pb.Units) + if err != nil { + return spatial.StickyXY{}, err + } + r.Units = &val + } + return r, nil +} + +// StickyXYsToPB converts a slice of StickyXY to StickyXY. +func StickyXYsToPB(rs []spatial.StickyXY) ([]*StickyXY, error) { + result := make([]*StickyXY, len(rs)) + for i := range rs { + var err error + result[i], err = StickyXYToPB(rs[i]) + if err != nil { + return nil, err + } + } + return result, nil +} + +// StickyXYsFromPB converts a slice of StickyXY to StickyXY. +func StickyXYsFromPB(pbs []*StickyXY) ([]spatial.StickyXY, error) { + result := make([]spatial.StickyXY, len(pbs)) + for i, pb := range pbs { + var err error + result[i], err = StickyXYFromPB(pb) + if err != nil { + return nil, err + } + } + return result, nil +} + +// DimensionsToPB converts Dimensions to Dimensions. +func DimensionsToPB(r spatial.Dimensions) (*Dimensions, error) { + pb := &Dimensions{ + Width: r.Width, + Height: r.Height, + } + return pb, nil +} + +// DimensionsFromPB converts Dimensions to Dimensions. +func DimensionsFromPB(pb *Dimensions) (spatial.Dimensions, error) { + var r spatial.Dimensions + if pb == nil { + return r, nil + } + r.Width = pb.Width + r.Height = pb.Height + return r, nil +} + +// DimensionsListToPB converts a slice of Dimensions to Dimensions. +func DimensionsListToPB(rs []spatial.Dimensions) ([]*Dimensions, error) { + result := make([]*Dimensions, len(rs)) + for i := range rs { + var err error + result[i], err = DimensionsToPB(rs[i]) + if err != nil { + return nil, err + } + } + return result, nil +} + +// DimensionsListFromPB converts a slice of Dimensions to Dimensions. +func DimensionsListFromPB(pbs []*Dimensions) ([]spatial.Dimensions, error) { + result := make([]spatial.Dimensions, len(pbs)) + for i, pb := range pbs { + var err error + result[i], err = DimensionsFromPB(pb) + if err != nil { + return nil, err + } + } + return result, nil +} + +// ViewportToPB converts Viewport to Viewport. +func ViewportToPB(r spatial.Viewport) (*Viewport, error) { + positionVal, err := XYToPB(r.Position) + if err != nil { + return nil, err + } + pb := &Viewport{ + Zoom: r.Zoom, + Position: positionVal, + } + return pb, nil +} + +// ViewportFromPB converts Viewport to Viewport. +func ViewportFromPB(pb *Viewport) (spatial.Viewport, error) { + var r spatial.Viewport + if pb == nil { + return r, nil + } + var err error + r.Position, err = XYFromPB(pb.Position) + if err != nil { + return spatial.Viewport{}, err + } + r.Zoom = pb.Zoom + return r, nil +} + +// ViewportsToPB converts a slice of Viewport to Viewport. +func ViewportsToPB(rs []spatial.Viewport) ([]*Viewport, error) { + result := make([]*Viewport, len(rs)) + for i := range rs { + var err error + result[i], err = ViewportToPB(rs[i]) + if err != nil { + return nil, err + } + } + return result, nil +} + +// ViewportsFromPB converts a slice of Viewport to Viewport. +func ViewportsFromPB(pbs []*Viewport) ([]spatial.Viewport, error) { + result := make([]spatial.Viewport, len(pbs)) + for i, pb := range pbs { + var err error + result[i], err = ViewportFromPB(pb) + if err != nil { + return nil, err + } + } + return result, nil +} + +// SignedDimensionsToPB converts SignedDimensions to SignedDimensions. +func SignedDimensionsToPB(r spatial.SignedDimensions) (*SignedDimensions, error) { + pb := &SignedDimensions{ + SignedWidth: r.SignedWidth, + SignedHeight: r.SignedHeight, + } + return pb, nil +} + +// SignedDimensionsFromPB converts SignedDimensions to SignedDimensions. +func SignedDimensionsFromPB(pb *SignedDimensions) (spatial.SignedDimensions, error) { + var r spatial.SignedDimensions + if pb == nil { + return r, nil + } + r.SignedWidth = pb.SignedWidth + r.SignedHeight = pb.SignedHeight + return r, nil +} + +// SignedDimensionsListToPB converts a slice of SignedDimensions to SignedDimensions. +func SignedDimensionsListToPB(rs []spatial.SignedDimensions) ([]*SignedDimensions, error) { + result := make([]*SignedDimensions, len(rs)) + for i := range rs { + var err error + result[i], err = SignedDimensionsToPB(rs[i]) + if err != nil { + return nil, err + } + } + return result, nil +} + +// SignedDimensionsListFromPB converts a slice of SignedDimensions to SignedDimensions. +func SignedDimensionsListFromPB(pbs []*SignedDimensions) ([]spatial.SignedDimensions, error) { + result := make([]spatial.SignedDimensions, len(pbs)) + for i, pb := range pbs { + var err error + result[i], err = SignedDimensionsFromPB(pb) + if err != nil { + return nil, err + } + } + return result, nil +} + +// ClientXYToPB converts ClientXY to ClientXY. +func ClientXYToPB(r spatial.ClientXY) (*ClientXY, error) { + pb := &ClientXY{ + ClientX: r.ClientX, + ClientY: r.ClientY, + } + return pb, nil +} + +// ClientXYFromPB converts ClientXY to ClientXY. +func ClientXYFromPB(pb *ClientXY) (spatial.ClientXY, error) { + var r spatial.ClientXY + if pb == nil { + return r, nil + } + r.ClientX = pb.ClientX + r.ClientY = pb.ClientY + return r, nil +} + +// ClientXYsToPB converts a slice of ClientXY to ClientXY. +func ClientXYsToPB(rs []spatial.ClientXY) ([]*ClientXY, error) { + result := make([]*ClientXY, len(rs)) + for i := range rs { + var err error + result[i], err = ClientXYToPB(rs[i]) + if err != nil { + return nil, err + } + } + return result, nil +} + +// ClientXYsFromPB converts a slice of ClientXY to ClientXY. +func ClientXYsFromPB(pbs []*ClientXY) ([]spatial.ClientXY, error) { + result := make([]spatial.ClientXY, len(pbs)) + for i, pb := range pbs { + var err error + result[i], err = ClientXYFromPB(pb) + if err != nil { + return nil, err + } + } + return result, nil +} + +// AlignmentToPB converts spatial.Alignment to Alignment. +func AlignmentToPB(v spatial.Alignment) (Alignment, error) { + switch v { + case spatial.AlignmentStart: + return Alignment_ALIGNMENT_START, nil + case spatial.AlignmentCenter: + return Alignment_ALIGNMENT_CENTER, nil + case spatial.AlignmentEnd: + return Alignment_ALIGNMENT_END, nil + default: + return 0, errors.Newf("unrecognized spatial.Alignment value: %v", v) + } +} + +// AlignmentFromPB converts Alignment to spatial.Alignment. +func AlignmentFromPB(v Alignment) (spatial.Alignment, error) { + switch v { + case Alignment_ALIGNMENT_START: + return spatial.AlignmentStart, nil + case Alignment_ALIGNMENT_CENTER: + return spatial.AlignmentCenter, nil + case Alignment_ALIGNMENT_END: + return spatial.AlignmentEnd, nil + default: + return spatial.Alignment(""), errors.Newf("unrecognized Alignment value: %v", v) + } +} + +// AngularDirectionToPB converts spatial.AngularDirection to AngularDirection. +func AngularDirectionToPB(v spatial.AngularDirection) (AngularDirection, error) { + switch v { + case spatial.AngularDirectionClockwise: + return AngularDirection_ANGULAR_DIRECTION_CLOCKWISE, nil + case spatial.AngularDirectionCounterclockwise: + return AngularDirection_ANGULAR_DIRECTION_COUNTERCLOCKWISE, nil + default: + return 0, errors.Newf("unrecognized spatial.AngularDirection value: %v", v) + } +} + +// AngularDirectionFromPB converts AngularDirection to spatial.AngularDirection. +func AngularDirectionFromPB(v AngularDirection) (spatial.AngularDirection, error) { + switch v { + case AngularDirection_ANGULAR_DIRECTION_CLOCKWISE: + return spatial.AngularDirectionClockwise, nil + case AngularDirection_ANGULAR_DIRECTION_COUNTERCLOCKWISE: + return spatial.AngularDirectionCounterclockwise, nil + default: + return spatial.AngularDirection(""), errors.Newf("unrecognized AngularDirection value: %v", v) + } +} + +// CenterLocationToPB converts spatial.CenterLocation to CenterLocation. +func CenterLocationToPB(v spatial.CenterLocation) (CenterLocation, error) { + switch v { + case spatial.CenterLocationCenter: + return CenterLocation_CENTER_LOCATION_CENTER, nil + default: + return 0, errors.Newf("unrecognized spatial.CenterLocation value: %v", v) + } +} + +// CenterLocationFromPB converts CenterLocation to spatial.CenterLocation. +func CenterLocationFromPB(v CenterLocation) (spatial.CenterLocation, error) { + switch v { + case CenterLocation_CENTER_LOCATION_CENTER: + return spatial.CenterLocationCenter, nil + default: + return spatial.CenterLocation(""), errors.Newf("unrecognized CenterLocation value: %v", v) + } +} + +// DimensionToPB converts spatial.Dimension to Dimension. +func DimensionToPB(v spatial.Dimension) (Dimension, error) { + switch v { + case spatial.DimensionWidth: + return Dimension_DIMENSION_WIDTH, nil + case spatial.DimensionHeight: + return Dimension_DIMENSION_HEIGHT, nil + default: + return 0, errors.Newf("unrecognized spatial.Dimension value: %v", v) + } +} + +// DimensionFromPB converts Dimension to spatial.Dimension. +func DimensionFromPB(v Dimension) (spatial.Dimension, error) { + switch v { + case Dimension_DIMENSION_WIDTH: + return spatial.DimensionWidth, nil + case Dimension_DIMENSION_HEIGHT: + return spatial.DimensionHeight, nil + default: + return spatial.Dimension(""), errors.Newf("unrecognized Dimension value: %v", v) + } +} + +// DirectionToPB converts spatial.Direction to Direction. +func DirectionToPB(v spatial.Direction) (Direction, error) { + switch v { + case spatial.DirectionX: + return Direction_DIRECTION_X, nil + case spatial.DirectionY: + return Direction_DIRECTION_Y, nil + default: + return 0, errors.Newf("unrecognized spatial.Direction value: %v", v) + } +} + +// DirectionFromPB converts Direction to spatial.Direction. +func DirectionFromPB(v Direction) (spatial.Direction, error) { + switch v { + case Direction_DIRECTION_X: + return spatial.DirectionX, nil + case Direction_DIRECTION_Y: + return spatial.DirectionY, nil + default: + return spatial.Direction(""), errors.Newf("unrecognized Direction value: %v", v) + } +} + +// LocationToPB converts spatial.Location to Location. +func LocationToPB(v spatial.Location) (Location, error) { + switch v { + case spatial.LocationTop: + return Location_LOCATION_TOP, nil + case spatial.LocationRight: + return Location_LOCATION_RIGHT, nil + case spatial.LocationBottom: + return Location_LOCATION_BOTTOM, nil + case spatial.LocationLeft: + return Location_LOCATION_LEFT, nil + case spatial.LocationCenter: + return Location_LOCATION_CENTER, nil + default: + return 0, errors.Newf("unrecognized spatial.Location value: %v", v) + } +} + +// LocationFromPB converts Location to spatial.Location. +func LocationFromPB(v Location) (spatial.Location, error) { + switch v { + case Location_LOCATION_TOP: + return spatial.LocationTop, nil + case Location_LOCATION_RIGHT: + return spatial.LocationRight, nil + case Location_LOCATION_BOTTOM: + return spatial.LocationBottom, nil + case Location_LOCATION_LEFT: + return spatial.LocationLeft, nil + case Location_LOCATION_CENTER: + return spatial.LocationCenter, nil + default: + return spatial.Location(""), errors.Newf("unrecognized Location value: %v", v) + } +} + +// OrderToPB converts spatial.Order to Order. +func OrderToPB(v spatial.Order) (Order, error) { + switch v { + case spatial.OrderFirst: + return Order_ORDER_FIRST, nil + case spatial.OrderLast: + return Order_ORDER_LAST, nil + default: + return 0, errors.Newf("unrecognized spatial.Order value: %v", v) + } +} + +// OrderFromPB converts Order to spatial.Order. +func OrderFromPB(v Order) (spatial.Order, error) { + switch v { + case Order_ORDER_FIRST: + return spatial.OrderFirst, nil + case Order_ORDER_LAST: + return spatial.OrderLast, nil + default: + return spatial.Order(""), errors.Newf("unrecognized Order value: %v", v) + } +} + // OuterLocationToPB converts spatial.OuterLocation to OuterLocation. func OuterLocationToPB(v spatial.OuterLocation) (OuterLocation, error) { switch v { @@ -93,3 +661,153 @@ func OuterLocationFromPB(v OuterLocation) (spatial.OuterLocation, error) { return spatial.OuterLocation(""), errors.Newf("unrecognized OuterLocation value: %v", v) } } + +// SignedDimensionToPB converts spatial.SignedDimension to SignedDimension. +func SignedDimensionToPB(v spatial.SignedDimension) (SignedDimension, error) { + switch v { + case spatial.SignedDimensionSignedWidth: + return SignedDimension_SIGNED_DIMENSION_SIGNED_WIDTH, nil + case spatial.SignedDimensionSignedHeight: + return SignedDimension_SIGNED_DIMENSION_SIGNED_HEIGHT, nil + default: + return 0, errors.Newf("unrecognized spatial.SignedDimension value: %v", v) + } +} + +// SignedDimensionFromPB converts SignedDimension to spatial.SignedDimension. +func SignedDimensionFromPB(v SignedDimension) (spatial.SignedDimension, error) { + switch v { + case SignedDimension_SIGNED_DIMENSION_SIGNED_WIDTH: + return spatial.SignedDimensionSignedWidth, nil + case SignedDimension_SIGNED_DIMENSION_SIGNED_HEIGHT: + return spatial.SignedDimensionSignedHeight, nil + default: + return spatial.SignedDimension(""), errors.Newf("unrecognized SignedDimension value: %v", v) + } +} + +// StickyUnitToPB converts spatial.StickyUnit to StickyUnit. +func StickyUnitToPB(v spatial.StickyUnit) (StickyUnit, error) { + switch v { + case spatial.StickyUnitPx: + return StickyUnit_STICKY_UNIT_PX, nil + case spatial.StickyUnitDecimal: + return StickyUnit_STICKY_UNIT_DECIMAL, nil + default: + return 0, errors.Newf("unrecognized spatial.StickyUnit value: %v", v) + } +} + +// StickyUnitFromPB converts StickyUnit to spatial.StickyUnit. +func StickyUnitFromPB(v StickyUnit) (spatial.StickyUnit, error) { + switch v { + case StickyUnit_STICKY_UNIT_PX: + return spatial.StickyUnitPx, nil + case StickyUnit_STICKY_UNIT_DECIMAL: + return spatial.StickyUnitDecimal, nil + default: + return spatial.StickyUnit(""), errors.Newf("unrecognized StickyUnit value: %v", v) + } +} + +// XLocationToPB converts spatial.XLocation to XLocation. +func XLocationToPB(v spatial.XLocation) (XLocation, error) { + switch v { + case spatial.XLocationLeft: + return XLocation_X_LOCATION_LEFT, nil + case spatial.XLocationRight: + return XLocation_X_LOCATION_RIGHT, nil + default: + return 0, errors.Newf("unrecognized spatial.XLocation value: %v", v) + } +} + +// XLocationFromPB converts XLocation to spatial.XLocation. +func XLocationFromPB(v XLocation) (spatial.XLocation, error) { + switch v { + case XLocation_X_LOCATION_LEFT: + return spatial.XLocationLeft, nil + case XLocation_X_LOCATION_RIGHT: + return spatial.XLocationRight, nil + default: + return spatial.XLocation(""), errors.Newf("unrecognized XLocation value: %v", v) + } +} + +// YLocationToPB converts spatial.YLocation to YLocation. +func YLocationToPB(v spatial.YLocation) (YLocation, error) { + switch v { + case spatial.YLocationTop: + return YLocation_Y_LOCATION_TOP, nil + case spatial.YLocationBottom: + return YLocation_Y_LOCATION_BOTTOM, nil + default: + return 0, errors.Newf("unrecognized spatial.YLocation value: %v", v) + } +} + +// YLocationFromPB converts YLocation to spatial.YLocation. +func YLocationFromPB(v YLocation) (spatial.YLocation, error) { + switch v { + case YLocation_Y_LOCATION_TOP: + return spatial.YLocationTop, nil + case YLocation_Y_LOCATION_BOTTOM: + return spatial.YLocationBottom, nil + default: + return spatial.YLocation(""), errors.Newf("unrecognized YLocation value: %v", v) + } +} + +// BoundsToPB converts Bounds to Bounds using provided type converters. +func BoundsToPB( + r spatial.Bounds, +) (*Bounds, error) { + pb := &Bounds{ + Lower: r.Lower, + Upper: r.Upper, + } + return pb, nil +} + +// BoundsFromPB converts Bounds to Bounds using provided type converters. +func BoundsFromPB( + pb *Bounds, +) (spatial.Bounds, error) { + var r spatial.Bounds + if pb == nil { + return r, nil + } + r.Lower = pb.Lower + r.Upper = pb.Upper + return r, nil +} + +// BoundsListToPB converts a slice of Bounds to Bounds. +func BoundsListToPB( + rs []spatial.Bounds, +) ([]*Bounds, error) { + result := make([]*Bounds, len(rs)) + for i := range rs { + var err error + result[i], err = BoundsToPB(rs[i]) + if err != nil { + return nil, err + } + } + return result, nil +} + +// BoundsListFromPB converts a slice of Bounds to Bounds. +func BoundsListFromPB( + pbs []*Bounds, +) ([]spatial.Bounds, error) { + result := make([]spatial.Bounds, len(pbs)) + for i, pb := range pbs { + var err error + result[i], err = BoundsFromPB(pb) + if err != nil { + return nil, err + } + } + return result, nil +} diff --git a/x/go/spatial/types.gen.go b/x/go/spatial/types.gen.go index 4cefc5abef..0293989733 100644 --- a/x/go/spatial/types.gen.go +++ b/x/go/spatial/types.gen.go @@ -11,6 +11,31 @@ package spatial +// XLocation is a horizontal-axis location at the left or right edge. +type XLocation string + +const ( + XLocationLeft XLocation = "left" + XLocationRight XLocation = "right" +) + +// YLocation is a vertical-axis location at the top or bottom edge. +type YLocation string + +const ( + YLocationTop YLocation = "top" + YLocationBottom YLocation = "bottom" +) + +// StickyUnit is the measurement unit for a sticky coordinate, either pixels or a +// decimal fraction of the container. +type StickyUnit string + +const ( + StickyUnitPx StickyUnit = "px" + StickyUnitDecimal StickyUnit = "decimal" +) + // OuterLocation is a position indicator for elements anchored to the outer edge of a // container. Used for orientation and positioning of UI elements. type OuterLocation string @@ -22,6 +47,75 @@ const ( OuterLocationLeft OuterLocation = "left" ) +// Direction is a 2D axis direction. +type Direction string + +const ( + DirectionX Direction = "x" + DirectionY Direction = "y" +) + +// AngularDirection is a rotational direction in 2D space. +type AngularDirection string + +const ( + AngularDirectionClockwise AngularDirection = "clockwise" + AngularDirectionCounterclockwise AngularDirection = "counterclockwise" +) + +// CenterLocation is a location at the center of a container. +type CenterLocation string + +const ( + CenterLocationCenter CenterLocation = "center" +) + +// Location is a position indicator covering the four outer edges of a container and its +// center. +type Location string + +const ( + LocationTop Location = "top" + LocationRight Location = "right" + LocationBottom Location = "bottom" + LocationLeft Location = "left" + LocationCenter Location = "center" +) + +// Alignment is a positioning indicator for aligning content along an axis within a +// container. +type Alignment string + +const ( + AlignmentStart Alignment = "start" + AlignmentCenter Alignment = "center" + AlignmentEnd Alignment = "end" +) + +// Order is a positional ordering indicator for elements in a sequence. +type Order string + +const ( + OrderFirst Order = "first" + OrderLast Order = "last" +) + +// Dimension is the name of a 2D size axis. +type Dimension string + +const ( + DimensionWidth Dimension = "width" + DimensionHeight Dimension = "height" +) + +// SignedDimension is the name of a 2D signed size axis. +type SignedDimension string + +const ( + SignedDimensionSignedWidth SignedDimension = "signedWidth" + SignedDimensionSignedHeight SignedDimension = "signedHeight" +) + // XY is a 2D coordinate point with x and y values. Used for positioning elements in // two-dimensional space. type XY struct { @@ -30,3 +124,76 @@ type XY struct { // Y is the vertical coordinate. Y float64 `json:"y" msgpack:"y"` } + +// CornerLocation is an anchor corner for positioning. +type CornerLocation struct { + // X is the horizontal anchor. + X XLocation `json:"x" msgpack:"x"` + // Y is the vertical anchor. + Y YLocation `json:"y" msgpack:"y"` +} + +// StickyUnits specifies the measurement units for sticky positioning. +type StickyUnits struct { + // X is the horizontal unit. + X StickyUnit `json:"x" msgpack:"x"` + // Y is the vertical unit. + Y StickyUnit `json:"y" msgpack:"y"` +} + +// StickyXY is a position that can be anchored to different corners of a container with +// configurable units (pixels or decimal fractions). +type StickyXY struct { + // X is the horizontal coordinate. + X float64 `json:"x" msgpack:"x"` + // Y is the vertical coordinate. + Y float64 `json:"y" msgpack:"y"` + // Root is the optional anchor corner for the position. + Root *CornerLocation `json:"root,omitempty" msgpack:"root,omitempty"` + // Units is the optional unit specification for the coordinates. + Units *StickyUnits `json:"units,omitempty" msgpack:"units,omitempty"` +} + +// Dimensions is a 2D size with width and height values. +type Dimensions struct { + // Width is the width in pixels. + Width float64 `json:"width" msgpack:"width"` + // Height is the height in pixels. + Height float64 `json:"height" msgpack:"height"` +} + +// Viewport is the camera state of a viewport. +type Viewport struct { + // Zoom is the zoom level where 1.0 equals 100%. + Zoom float64 `json:"zoom" msgpack:"zoom"` + // Position is the (x, y) pan offset of the viewport. + Position XY `json:"position" msgpack:"position"` +} + +// SignedDimensions is a 2D size whose width and height components carry sign, allowing +// negative values to express direction. +type SignedDimensions struct { + // SignedWidth is the signed width. + SignedWidth float64 `json:"signed_width" msgpack:"signed_width"` + // SignedHeight is the signed height. + SignedHeight float64 `json:"signed_height" msgpack:"signed_height"` +} + +// ClientXY is a 2D coordinate point expressed in client (viewport) space, matching the +// shape of DOM mouse events. +type ClientXY struct { + // ClientX is the horizontal coordinate in client (viewport) space. + ClientX float64 `json:"client_x" msgpack:"client_x"` + // ClientY is the vertical coordinate in client (viewport) space. + ClientY float64 `json:"client_y" msgpack:"client_y"` +} + +// Bounds is a closed-open interval [lower, upper) over an ordered numeric value space. +// The TypeScript binding is generic over T so callers can express bounds over either +// number or bigint values; other languages emit a concrete float64-based type. +type Bounds struct { + // Lower is the inclusive lower bound. + Lower float64 `json:"lower" msgpack:"lower"` + // Upper is the exclusive upper bound. + Upper float64 `json:"upper" msgpack:"upper"` +} diff --git a/x/ts/src/index.ts b/x/ts/src/index.ts index 4f9827e12c..8d0092104d 100644 --- a/x/ts/src/index.ts +++ b/x/ts/src/index.ts @@ -33,6 +33,7 @@ export * from "@/math"; export * from "@/migrate"; export * from "@/narrow"; export * from "@/notation"; +export * from "@/numeric"; export * from "@/observe"; export * from "@/optional"; export * from "@/primitive"; diff --git a/x/ts/src/spatial/base.ts b/x/ts/src/spatial/base.ts index 92abe88ffa..a4ad74eb4c 100644 --- a/x/ts/src/spatial/base.ts +++ b/x/ts/src/spatial/base.ts @@ -9,99 +9,7 @@ import { z } from "zod"; -// Tuples +export * from "@/spatial/types.gen"; export const numberCouple = z.tuple([z.number(), z.number()]); export type NumberCouple = [T, T]; - -// Direction - -export const DIRECTIONS = ["x", "y"] as const; -export const directionZ = z.enum(DIRECTIONS); -export type Direction = z.infer; - -// Location - -export const OUTER_LOCATIONS = ["top", "right", "bottom", "left"] as const; -export const outerLocationZ = z.enum(OUTER_LOCATIONS); -export type OuterLocation = z.infer; - -export const X_LOCATIONS = ["left", "right"] as const; -export const xLocationZ = z.enum(X_LOCATIONS); -export type XLocation = z.infer; - -export const Y_LOCATIONS = ["top", "bottom"] as const; -export const yLocationZ = z.enum(Y_LOCATIONS); -export type YLocation = z.infer; - -export const CENTER_LOCATIONS = ["center"] as const; -export const centerLocationZ = z.enum(CENTER_LOCATIONS); -export type CenterLocation = z.infer; - -export const LOCATIONS = ["top", "right", "bottom", "left", "center"] as const; -export const locationZ = z.enum(LOCATIONS); -export type Location = z.infer; - -// Alignment - -export const ALIGNMENTS = ["start", "center", "end"] as const; -export const alignmentZ = z.enum(ALIGNMENTS); -export type Alignment = z.infer; - -// Order - -export const ORDERS = ["first", "last"] as const; -export const orderZ = z.enum(ORDERS); -export type Order = z.infer; - -// XY - -export const clientXyZ = z.object({ clientX: z.number(), clientY: z.number() }); -export type ClientXY = z.infer; - -// Dimensions - -export const dimensionsZ = z.object({ width: z.number(), height: z.number() }); -export type Dimensions = z.infer; - -export const signedDimensionsZ = z.object({ - signedWidth: z.number(), - signedHeight: z.number(), -}); -export type SignedDimensions = z.infer; - -export type Dimension = "width" | "height"; -export type SignedDimension = "signedWidth" | "signedHeight"; - -// Bounds - -export const boundsZ = z.object({ lower: z.number(), upper: z.number() }); - -// Generic bounds interface (supports bigint) -export interface Bounds { - lower: T; - upper: T; -} - -export type CrudeBounds = - | Bounds - | NumberCouple; - -// Derived/complex types - -export const crudeDirection = z.enum([ - "x", - "y", - ...OUTER_LOCATIONS, - ...CENTER_LOCATIONS, -]); -export type CrudeDirection = z.infer; -export type CrudeXDirection = "x" | "left" | "right"; -export type CrudeYDirection = "y" | "top" | "bottom"; -export type AngularDirection = "clockwise" | "counterclockwise"; -export const crudeLocation = z.union([ - directionZ, - z.enum([...OUTER_LOCATIONS, ...CENTER_LOCATIONS]), - z.instanceof(String), -]); -export type CrudeLocation = z.infer; diff --git a/x/ts/src/spatial/bounds/bounds.ts b/x/ts/src/spatial/bounds/bounds.ts index d140c7b5f7..524509dd38 100644 --- a/x/ts/src/spatial/bounds/bounds.ts +++ b/x/ts/src/spatial/bounds/bounds.ts @@ -9,11 +9,11 @@ import { abs, add, equal as mathEqual, min as mathMin, sub } from "@/math/math"; import { type numeric } from "@/numeric"; -import { type Bounds, boundsZ, type CrudeBounds } from "@/spatial/base"; +import { type Bounds, boundsZ, type NumberCouple } from "@/spatial/base"; export { type Bounds, boundsZ }; -export type Crude = CrudeBounds; +export type Crude = Bounds | NumberCouple; /** Options for the `construct` function. */ interface ConstructOptions { @@ -209,7 +209,7 @@ export const clamp = (bounds: Crude, target: T): T = */ export const contains = ( bounds: Crude, - target: T | CrudeBounds, + target: T | Crude, ): boolean => { const _bounds = construct(bounds); if (typeof target === "number" || typeof target === "bigint") diff --git a/x/ts/src/spatial/box/box.ts b/x/ts/src/spatial/box/box.ts index afc58b6af6..7bf96b255f 100644 --- a/x/ts/src/spatial/box/box.ts +++ b/x/ts/src/spatial/box/box.ts @@ -32,7 +32,7 @@ export const domRect = z.object({ export const box = z.object({ one: xy.xyZ, two: xy.xyZ, - root: location.corner, + root: location.cornerZ, }); export type Box = z.infer; @@ -50,7 +50,7 @@ export const ZERO = { one: xy.ZERO, two: xy.ZERO, root: location.TOP_LEFT }; */ export const DECIMAL = { one: xy.ZERO, two: xy.ONE, root: location.BOTTOM_LEFT }; -export const copy = (b: Box, root?: location.CornerXY): Box => ({ +export const copy = (b: Box, root?: location.Corner): Box => ({ one: b.one, two: b.two, root: root ?? b.root, @@ -71,7 +71,7 @@ export const construct = ( second?: number | xy.XY | dimensions.Dimensions | dimensions.Signed, width: number = 0, height: number = 0, - coordinateRoot?: location.CornerXY, + coordinateRoot?: location.Corner, ): Box => { const b: Box = { one: { ...xy.ZERO }, @@ -339,7 +339,7 @@ export const yBounds = (b: Crude): bounds.Bounds => { return { lower: b_.one.y, upper: b_.two.y }; }; -export const reRoot = (b: Box, corner: location.CornerXY): Box => copy(b, corner); +export const reRoot = (b: Box, corner: location.Corner): Box => copy(b, corner); export const edgePoints = (b: Crude, loc: location.Location): [xy.XY, xy.XY] => { const b_ = construct(b); @@ -461,7 +461,7 @@ export const constructWithAlternateRoot = ( width: number, height: number, currRoot: location.XY, - newRoot: location.CornerXY, + newRoot: location.Corner, ): Box => { const first = { x, y }; const second = { x: x + width, y: y + height }; diff --git a/x/ts/src/spatial/direction/direction.ts b/x/ts/src/spatial/direction/direction.ts index e0f9294a5a..080a632df3 100644 --- a/x/ts/src/spatial/direction/direction.ts +++ b/x/ts/src/spatial/direction/direction.ts @@ -7,30 +7,29 @@ // License, use of this software will be governed by the Apache License, Version 2.0, // included in the file licenses/APL.txt. +import z from "zod"; + import { type AngularDirection, - type CrudeDirection, - crudeDirection, - type CrudeXDirection, - type CrudeYDirection, + CENTER_LOCATIONS, type Dimension, type Direction, DIRECTIONS, directionZ, type Location, + OUTER_LOCATIONS, type SignedDimension, Y_LOCATIONS, type YLocation, -} from "@/spatial/base"; +} from "@/spatial/types.gen"; export { type Direction, DIRECTIONS, directionZ }; -export const crude = crudeDirection; - -export type Crude = CrudeDirection; -export type CrudeX = CrudeXDirection; -export type CrudeY = CrudeYDirection; export type Angular = AngularDirection; +export const crudeZ = z.enum(["x", "y", ...OUTER_LOCATIONS, ...CENTER_LOCATIONS]); +export type Crude = z.infer; +export type CrudeX = "x" | "left" | "right"; +export type CrudeY = "y" | "top" | "bottom"; export const construct = (c: Crude): Direction => { if (DIRECTIONS.includes(c as Direction)) return c as Direction; @@ -38,24 +37,24 @@ export const construct = (c: Crude): Direction => { return "x"; }; -export const swap = (direction: CrudeDirection): Direction => +export const swap = (direction: Crude): Direction => construct(direction) === "x" ? "y" : "x"; -export const dimension = (direction: CrudeDirection): Dimension => +export const dimension = (direction: Crude): Dimension => construct(direction) === "x" ? "width" : "height"; -export const location = (direction: CrudeDirection): Location => +export const location = (direction: Crude): Location => construct(direction) === "x" ? "left" : "top"; -export const isDirection = (c: unknown): c is Direction => crude.safeParse(c).success; +export const isDirection = (c: unknown): c is Direction => crudeZ.safeParse(c).success; -export const signedDimension = (direction: CrudeDirection): SignedDimension => +export const signedDimension = (direction: Crude): SignedDimension => construct(direction) === "x" ? "signedWidth" : "signedHeight"; -export const isX = (direction: CrudeDirection): direction is CrudeXDirection => { +export const isX = (direction: Crude): direction is CrudeX => { if (direction === "center") return false; return construct(direction) === "x"; }; -export const isY = (direction: CrudeDirection): direction is CrudeYDirection => +export const isY = (direction: Crude): direction is CrudeY => construct(direction) === "y"; diff --git a/x/ts/src/spatial/external.ts b/x/ts/src/spatial/external.ts index 661c1d2c16..9b49da9106 100644 --- a/x/ts/src/spatial/external.ts +++ b/x/ts/src/spatial/external.ts @@ -13,7 +13,6 @@ export * from "@/spatial/dimensions"; export * from "@/spatial/direction"; export * from "@/spatial/location"; export * from "@/spatial/scale"; -export * as spatial from "@/spatial/spatial"; export * from "@/spatial/sticky"; -export * from "@/spatial/types.gen"; +export * as spatial from "@/spatial/types.gen"; export * from "@/spatial/xy"; diff --git a/x/ts/src/spatial/location/location.ts b/x/ts/src/spatial/location/location.ts index fad0df58ac..c663c565a0 100644 --- a/x/ts/src/spatial/location/location.ts +++ b/x/ts/src/spatial/location/location.ts @@ -15,10 +15,11 @@ import { CENTER_LOCATIONS, type CenterLocation, centerLocationZ, - type CrudeLocation, - crudeLocation, + type CornerLocation, + cornerLocationZ, type Direction, DIRECTIONS, + directionZ, type Location, LOCATIONS, locationZ, @@ -43,15 +44,18 @@ export { Y_LOCATIONS, }; -export const x = xLocationZ; -export const y = yLocationZ; -export const center = centerLocationZ; +export const xZ = xLocationZ; +export const yZ = yLocationZ; +export const centerZ = centerLocationZ; export const outerZ = outerLocationZ; +export const cornerZ = cornerLocationZ; export type X = XLocation; export type Y = YLocation; export type Outer = OuterLocation; export type Center = CenterLocation; +export type Corner = CornerLocation; +export type CornerString = "topLeft" | "topRight" | "bottomLeft" | "bottomRight"; const SWAPPED: Record = { top: "bottom", @@ -67,10 +71,12 @@ const ROTATIONS: Record> = { bottom: { clockwise: "right", counterclockwise: "left" }, left: { clockwise: "bottom", counterclockwise: "top" }, }; - -export const crude = crudeLocation; - -export type Crude = CrudeLocation; +export const crudeZ = z.union([ + directionZ, + z.enum([...OUTER_LOCATIONS, ...CENTER_LOCATIONS]), + z.instanceof(String), +]); +export type Crude = z.infer; export const construct = (cl: Crude): Location => { if (cl instanceof String) return cl as Location; @@ -93,16 +99,12 @@ export const xy = z.object({ x: xLocationZ.or(centerLocationZ), y: yLocationZ.or(centerLocationZ), }); -export const corner = z.object({ x: xLocationZ, y: yLocationZ }); - export type XY = z.infer; -export type CornerXY = z.infer; -export type CornerXYString = "topLeft" | "topRight" | "bottomLeft" | "bottomRight"; -export const TOP_LEFT: CornerXY = Object.freeze({ x: "left", y: "top" }); -export const TOP_RIGHT: CornerXY = Object.freeze({ x: "right", y: "top" }); -export const BOTTOM_LEFT: CornerXY = Object.freeze({ x: "left", y: "bottom" }); -export const BOTTOM_RIGHT: CornerXY = Object.freeze({ x: "right", y: "bottom" }); +export const TOP_LEFT: Corner = Object.freeze({ x: "left", y: "top" }); +export const TOP_RIGHT: Corner = Object.freeze({ x: "right", y: "top" }); +export const BOTTOM_LEFT: Corner = Object.freeze({ x: "left", y: "bottom" }); +export const BOTTOM_RIGHT: Corner = Object.freeze({ x: "right", y: "bottom" }); export const CENTER: XY = Object.freeze({ x: "center", y: "center" }); export const TOP_CENTER: XY = Object.freeze({ x: "center", y: "top" }); export const BOTTOM_CENTER: XY = Object.freeze({ x: "center", y: "bottom" }); diff --git a/x/ts/src/spatial/scale/scale.ts b/x/ts/src/spatial/scale/scale.ts index f991d40891..4a478a7840 100644 --- a/x/ts/src/spatial/scale/scale.ts +++ b/x/ts/src/spatial/scale/scale.ts @@ -417,12 +417,12 @@ export class Scale { export class XY { x: Scale; y: Scale; - currRoot: location.CornerXY | null; + currRoot: location.Corner | null; constructor( x: Scale = new Scale(), y: Scale = new Scale(), - root: location.CornerXY | null = null, + root: location.Corner | null = null, ) { this.x = x; this.y = y; diff --git a/x/ts/src/spatial/spatial.ts b/x/ts/src/spatial/spatial.ts deleted file mode 100644 index 047697cee8..0000000000 --- a/x/ts/src/spatial/spatial.ts +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2026 Synnax Labs, Inc. -// -// Use of this software is governed by the Business Source License included in the file -// licenses/BSL.txt. -// -// As of the Change Date specified in that file, in accordance with the Business Source -// License, use of this software will be governed by the Apache License, Version 2.0, -// included in the file licenses/APL.txt. - -export { - type Alignment, - ALIGNMENTS, - alignmentZ, - type Bounds, - boundsZ, - CENTER_LOCATIONS, - type CenterLocation, - centerLocationZ, - type ClientXY, - clientXyZ, - type Dimensions, - dimensionsZ, - type Direction, - DIRECTIONS, - directionZ, - type Location, - LOCATIONS, - locationZ, - type Order, - ORDERS, - orderZ, - OUTER_LOCATIONS, - type OuterLocation, - outerLocationZ, - type SignedDimensions, - signedDimensionsZ, - X_LOCATIONS, - type XLocation, - xLocationZ, - Y_LOCATIONS, - type YLocation, - yLocationZ, -} from "@/spatial/base"; -export { type XY, xyZ } from "@/spatial/types.gen"; diff --git a/x/ts/src/spatial/sticky/sticky.spec.ts b/x/ts/src/spatial/sticky/sticky.spec.ts index f9fefb612b..2a6930fcfd 100644 --- a/x/ts/src/spatial/sticky/sticky.spec.ts +++ b/x/ts/src/spatial/sticky/sticky.spec.ts @@ -134,7 +134,7 @@ describe("sticky", () => { ]; SPECS.forEach(({ value, valid }, i) => { test(`xy schema ${i}`, () => { - const result = sticky.xy.safeParse(value); + const result = sticky.xyZ.safeParse(value); expect(result.success).toBe(valid); }); }); @@ -196,7 +196,7 @@ describe("sticky", () => { ]; SPECS.forEach(({ value, valid }, i) => { test(`completeXY schema ${i}`, () => { - const result = sticky.completeXY.safeParse(value); + const result = sticky.completeXYZ.safeParse(value); expect(result.success).toBe(valid); }); }); diff --git a/x/ts/src/spatial/sticky/sticky.ts b/x/ts/src/spatial/sticky/sticky.ts index 85813cf63e..5c78b32842 100644 --- a/x/ts/src/spatial/sticky/sticky.ts +++ b/x/ts/src/spatial/sticky/sticky.ts @@ -7,25 +7,18 @@ // License, use of this software will be governed by the Apache License, Version 2.0, // included in the file licenses/APL.txt. -import z from "zod"; +import type z from "zod"; import { box } from "@/spatial/box"; import { location } from "@/spatial/location"; +import { type StickyXY, stickyXYZ } from "@/spatial/types.gen"; import { xy as base } from "@/spatial/xy"; -export const completeXY = base.xyZ.extend({ - root: location.corner, - units: z.object({ - x: z.enum(["px", "decimal"]), - y: z.enum(["px", "decimal"]), - }), -}); +export const xyZ = stickyXYZ; +export type XY = StickyXY; -export type CompleteXY = z.infer; - -export const xy = completeXY.partial({ root: true, units: true }); - -export interface XY extends z.infer {} +export const completeXYZ = stickyXYZ.required({ root: true, units: true }); +export type CompleteXY = z.infer; interface ToCSSReturn extends Partial> {} diff --git a/x/ts/src/spatial/types.gen.ts b/x/ts/src/spatial/types.gen.ts index 77e177008c..8ab780e03a 100644 --- a/x/ts/src/spatial/types.gen.ts +++ b/x/ts/src/spatial/types.gen.ts @@ -11,10 +11,56 @@ import { z } from "zod"; +import { type numeric } from "@/numeric"; + +export const X_LOCATIONS = ["left", "right"] as const; +export const xLocationZ = z.enum(X_LOCATIONS); +export type XLocation = z.infer; + +export const Y_LOCATIONS = ["top", "bottom"] as const; +export const yLocationZ = z.enum(Y_LOCATIONS); +export type YLocation = z.infer; + +export const STICKY_UNITS = ["px", "decimal"] as const; +export const stickyUnitZ = z.enum(STICKY_UNITS); +export type StickyUnit = z.infer; + export const OUTER_LOCATIONS = ["top", "right", "bottom", "left"] as const; export const outerLocationZ = z.enum(OUTER_LOCATIONS); export type OuterLocation = z.infer; +export const DIRECTIONS = ["x", "y"] as const; +export const directionZ = z.enum(DIRECTIONS); +export type Direction = z.infer; + +export const ANGULAR_DIRECTIONS = ["clockwise", "counterclockwise"] as const; +export const angularDirectionZ = z.enum(ANGULAR_DIRECTIONS); +export type AngularDirection = z.infer; + +export const CENTER_LOCATIONS = ["center"] as const; +export const centerLocationZ = z.enum(CENTER_LOCATIONS); +export type CenterLocation = z.infer; + +export const LOCATIONS = ["top", "right", "bottom", "left", "center"] as const; +export const locationZ = z.enum(LOCATIONS); +export type Location = z.infer; + +export const ALIGNMENTS = ["start", "center", "end"] as const; +export const alignmentZ = z.enum(ALIGNMENTS); +export type Alignment = z.infer; + +export const ORDERS = ["first", "last"] as const; +export const orderZ = z.enum(ORDERS); +export type Order = z.infer; + +export const DIMENSIONS = ["width", "height"] as const; +export const dimensionZ = z.enum(DIMENSIONS); +export type Dimension = z.infer; + +export const SIGNED_DIMENSIONS = ["signedWidth", "signedHeight"] as const; +export const signedDimensionZ = z.enum(SIGNED_DIMENSIONS); +export type SignedDimension = z.infer; + /** * XY is a 2D coordinate point with x and y values. Used for positioning * elements in two-dimensional space. @@ -26,3 +72,97 @@ export const xyZ = z.object({ y: z.number(), }); export interface XY extends z.infer {} + +/** CornerLocation is an anchor corner for positioning. */ +export const cornerLocationZ = z.object({ + /** x is the horizontal anchor. */ + x: xLocationZ, + /** y is the vertical anchor. */ + y: yLocationZ, +}); +export interface CornerLocation extends z.infer {} + +/** StickyUnits specifies the measurement units for sticky positioning. */ +export const stickyUnitsZ = z.object({ + /** x is the horizontal unit. */ + x: stickyUnitZ, + /** y is the vertical unit. */ + y: stickyUnitZ, +}); +export interface StickyUnits extends z.infer {} + +/** Dimensions is a 2D size with width and height values. */ +export const dimensionsZ = z.object({ + /** width is the width in pixels. */ + width: z.number(), + /** height is the height in pixels. */ + height: z.number(), +}); +export interface Dimensions extends z.infer {} + +/** + * SignedDimensions is a 2D size whose width and height components carry sign, allowing + * negative values to express direction. + */ +export const signedDimensionsZ = z.object({ + /** signedWidth is the signed width. */ + signedWidth: z.number(), + /** signedHeight is the signed height. */ + signedHeight: z.number(), +}); +export interface SignedDimensions extends z.infer {} + +/** + * ClientXY is a 2D coordinate point expressed in client (viewport) space, matching + * the shape of DOM mouse events. + */ +export const clientXYZ = z.object({ + /** clientX is the horizontal coordinate in client (viewport) space. */ + clientX: z.number(), + /** clientY is the vertical coordinate in client (viewport) space. */ + clientY: z.number(), +}); +export interface ClientXY extends z.infer {} + +/** + * Bounds is a closed-open interval [lower, upper) over an ordered numeric value + * space. The TypeScript binding is generic over T so callers can express + * bounds over either number or bigint values; other languages emit a + * concrete float64-based type. + */ +export const boundsZ = (t?: z.ZodType) => + z.object({ + /** lower is the inclusive lower bound. */ + lower: t ?? z.number(), + /** upper is the exclusive upper bound. */ + upper: t ?? z.number(), + }); +export interface Bounds { + lower: T; + upper: T; +} + +/** Viewport is the camera state of a viewport. */ +export const viewportZ = z.object({ + /** zoom is the zoom level where 1.0 equals 100%. */ + zoom: z.number().default(1), + /** position is the (x, y) pan offset of the viewport. */ + position: xyZ, +}); +export interface Viewport extends z.infer {} + +/** + * StickyXY is a position that can be anchored to different corners of a + * container with configurable units (pixels or decimal fractions). + */ +export const stickyXYZ = z.object({ + /** x is the horizontal coordinate. */ + x: z.number(), + /** y is the vertical coordinate. */ + y: z.number(), + /** root is the optional anchor corner for the position. */ + root: cornerLocationZ.optional(), + /** units is the optional unit specification for the coordinates. */ + units: stickyUnitsZ.optional(), +}); +export interface StickyXY extends z.infer {} diff --git a/x/ts/src/spatial/xy/xy.ts b/x/ts/src/spatial/xy/xy.ts index e6f78f5df5..32367fbed4 100644 --- a/x/ts/src/spatial/xy/xy.ts +++ b/x/ts/src/spatial/xy/xy.ts @@ -12,19 +12,19 @@ import { z } from "zod"; import { type AngularDirection, type ClientXY, - clientXyZ, - type CrudeDirection, + clientXYZ, dimensionsZ, type Direction, type NumberCouple, numberCouple, signedDimensionsZ, + type XY, + xyZ, } from "@/spatial/base"; -import { direction as dir } from "@/spatial/direction"; +import { direction as dir, type direction } from "@/spatial/direction"; import { type location } from "@/spatial/location"; -import { type XY, xyZ } from "@/spatial/types.gen"; -export { type ClientXY as Client, clientXyZ, type XY, xyZ }; +export { type ClientXY as Client, clientXYZ, type XY, xyZ }; /** A crude representation of a {@link XY} coordinate as a zod schema. */ export const crudeZ = z.union([ @@ -33,7 +33,7 @@ export const crudeZ = z.union([ numberCouple, dimensionsZ, signedDimensionsZ, - clientXyZ, + clientXYZ, ]); /** A crude representation of a {@link XY} coordinate. */ @@ -141,7 +141,7 @@ export const translate: Translate = (a, b, v, ...cb): XY => { * @returns the given coordinate the given direction set to the given value. * @example set({ x: 1, y: 2 }, "x", 3) // { x: 3, y: 2 } */ -export const set = (c: Crude, direction: CrudeDirection, value: number): XY => { +export const set = (c: Crude, direction: direction.Crude, value: number): XY => { const xy = construct(c); const d = dir.construct(direction); if (d === "x") return { x: value, y: xy.y };