@quenty/textserviceutils 7.5.0 → 7.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,17 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [7.6.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/textserviceutils@7.5.0...@quenty/textserviceutils@7.6.0) (2022-12-27)
7
+
8
+
9
+ ### Features
10
+
11
+ * Add TextServiceUtils.promiseTextBounds with a workaround for Roblox cancelling certain calls in weird ways. ([9f91df0](https://github.com/Quenty/NevermoreEngine/commit/9f91df0ddf8edcac7c68945b22772a964621a5fa))
12
+
13
+
14
+
15
+
16
+
6
17
  # [7.5.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/textserviceutils@7.4.0...@quenty/textserviceutils@7.5.0) (2022-12-05)
7
18
 
8
19
  **Note:** Version bump only for package @quenty/textserviceutils
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quenty/textserviceutils",
3
- "version": "7.5.0",
3
+ "version": "7.6.0",
4
4
  "description": "Holds utilities involving the Roblox TextService and text fitting to size.",
5
5
  "keywords": [
6
6
  "Roblox",
@@ -32,7 +32,8 @@
32
32
  "dependencies": {
33
33
  "@quenty/blend": "^6.5.0",
34
34
  "@quenty/loader": "^6.0.1",
35
+ "@quenty/promise": "^6.0.1",
35
36
  "@quenty/rx": "^7.2.0"
36
37
  },
37
- "gitHead": "3040397f5f4601886ca7a1f664be7019b3941618"
38
+ "gitHead": "a4f99aa40c45080db30be7117cada229572f1574"
38
39
  }
@@ -7,9 +7,23 @@ local TextService = game:GetService("TextService")
7
7
  local require = require(script.Parent.loader).load(script)
8
8
 
9
9
  local Blend = require("Blend")
10
+ local Promise = require("Promise")
11
+ local Rx = require("Rx")
10
12
 
11
13
  local TextServiceUtils = {}
12
14
 
15
+ --[=[
16
+ Gets the size for the label using legacy API surface.
17
+
18
+ :::warning
19
+ This will not handle new font faces well.
20
+ :::
21
+
22
+ @param textLabel TextLabel
23
+ @param text string
24
+ @param maxWidth number
25
+ @return Promise<Vector2>
26
+ ]=]
13
27
  function TextServiceUtils.getSizeForLabel(textLabel, text, maxWidth)
14
28
  assert(typeof(textLabel) == "Instance", "Bad textLabel")
15
29
  assert(type(text) == "string", "Bad text")
@@ -20,17 +34,139 @@ function TextServiceUtils.getSizeForLabel(textLabel, text, maxWidth)
20
34
  return TextService:GetTextSize(text, textLabel.TextSize, textLabel.Font, Vector2.new(maxWidth, 1e6))
21
35
  end
22
36
 
37
+ local lastPromise = nil
38
+
39
+ --[=[
40
+ Promises the text bounds for the given parameters
41
+
42
+ @param params GetTextBoundsParams
43
+ @return Promise<Vector2>
44
+ ]=]
45
+ function TextServiceUtils.promiseTextBounds(params)
46
+ assert(typeof(params) == "Instance" and params:IsA("GetTextBoundsParams"), "Bad params")
47
+
48
+ -- https://devforum.roblox.com/t/calling-textservicegettextboundsasync-multiple-times-leads-to-requests-never-completing-thread-leaks/2083178
49
+ -- This is a hack to work around a Roblox bug.
50
+ local promise
51
+ if lastPromise then
52
+ promise = lastPromise:Finally(function()
53
+ return Promise.defer(function(resolve, reject)
54
+ resolve(TextServiceUtils._promiseTextBounds(params))
55
+
56
+ -- We don't want everything breaking
57
+ task.delay(5, function()
58
+ reject("Timed out")
59
+ end)
60
+ end)
61
+ end)
62
+ else
63
+ promise = TextServiceUtils._promiseTextBounds(params)
64
+
65
+ -- We don't want everything breaking
66
+ task.delay(5, function()
67
+ promise:Reject("Timed out")
68
+ end)
69
+ end
70
+
71
+
72
+ -- Store this so we act as a queue :)
73
+ lastPromise = promise
74
+
75
+ -- Clean up
76
+ promise:Finally(function()
77
+ task.defer(function()
78
+ if lastPromise == promise then
79
+ lastPromise = nil
80
+ end
81
+ end)
82
+ end)
83
+
84
+ return promise
85
+ end
86
+
87
+ function TextServiceUtils._promiseTextBounds(params)
88
+ assert(typeof(params) == "Instance" and params:IsA("GetTextBoundsParams"), "Bad params")
89
+
90
+ return Promise.spawn(function(resolve, reject)
91
+ local size
92
+ local ok, err = pcall(function()
93
+ size = TextService:GetTextBoundsAsync(params)
94
+ end)
95
+
96
+ if not ok then
97
+ return reject(err)
98
+ end
99
+
100
+ return resolve(size)
101
+ end)
102
+ end
103
+
104
+ --[=[
105
+ Observes the current size for the current props. The properties
106
+ can be anything [Blend] would accept as an input. If FontFace is defined,
107
+ it will be used before Font. The following properties are available:
108
+
109
+ * `Text` - string
110
+ * `TextSize` - number
111
+ * `Font` - [Enum.Font]
112
+ * `FontFace` [Font]
113
+ * `MaxSize` - [Vector2]
114
+ * `LineHeight` - number
115
+
116
+ ```lua
117
+ local stringValue = Instance.new("StringValue")
118
+ stringValue.Text = "Hello"
119
+
120
+ local observe = TextServiceUtils.observeSizeForLabelProps({
121
+ Text = stringValue;
122
+ Font = Enum.Font.;
123
+ MaxSize = Vector2.new(250, 100);
124
+ TextSize = 24;
125
+ })
126
+
127
+ -- Be sure to clean up the subscription
128
+ observe:Subscribe(function(size)
129
+ print(size)
130
+ end)
131
+
132
+ ```
133
+
134
+ @param props table
135
+ @return Observable<Vector2> -- The text bounds reported
136
+ ]=]
23
137
  function TextServiceUtils.observeSizeForLabelProps(props)
24
138
  assert(props.Text, "Bad props.Text")
25
139
  assert(props.TextSize, "Bad props.TextSize")
26
- assert(props.Font, "Bad props.Font")
27
140
 
28
- return Blend.Computed(props.Text, props.TextSize, props.Font, props.MaxSize or Vector2.new(1e6, 1e6), props.LineHeight or 1,
29
- function(text, textSize, font, maxSize, lineHeight)
30
- local size = TextService:GetTextSize(text, textSize, font, maxSize)
141
+ if not (props.Font or props.FontFace) then
142
+ error("Bad props.Font or props.FontFace")
143
+ end
144
+
145
+ return Rx.combineLatest({
146
+ Text = Blend.toPropertyObservable(props.Text) or props.Text,
147
+ TextSize = Blend.toPropertyObservable(props.TextSize) or props.TextSize,
148
+ Font = Blend.toPropertyObservable(props.Font) or props.Font,
149
+ FontFace = Blend.toPropertyObservable(props.FontFace) or props.FontFace,
150
+ MaxSize = Blend.toPropertyObservable(props.MaxSize) or props.MaxSize or Vector2.new(1e6, 1e6),
151
+ LineHeight = Blend.toPropertyObservable(props.LineHeight) or 1,
152
+ }):Pipe({
153
+ Rx.switchMap(function(state)
154
+ if typeof(state.FontFace) == "Font" then
155
+ -- Yes, our font may have to stream in
156
+ local params = Instance.new("GetTextBoundsParams")
157
+ params.Text = state.Text
158
+ params.Size = state.TextSize
159
+ params.Font = state.Font
160
+ params.Width = state.MaxSize.x
161
+
162
+ return Rx.fromPromise(TextServiceUtils.promiseTextBounds(params))
163
+ elseif typeof(state.Font) == "EnumItem" then
164
+ local size = TextService:GetTextSize(state.Text, state.TextSize, state.Font, state.MaxSize)
31
165
 
32
- return Vector2.new(size.x, lineHeight*size.y)
166
+ return Rx.of(Vector2.new(size.x, state.LineHeight*size.y))
167
+ end
33
168
  end)
169
+ })
34
170
  end
35
171
 
36
172
  return TextServiceUtils